sbmt-outbox 6.6.0 → 6.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a86388ca6b13cdaef9411bd8baf1ea045fcc1b941721cdb8d4fb15bda4db29be
4
- data.tar.gz: 6fbc5a6e5d62df9aaf1cf2f0afdc3cf49e8bf5ccdc0a49d86a0b8feb8e51f200
3
+ metadata.gz: 7eeda1fb96153d344c166cc65ba9dd8cfcfdfb1402184258e37efff70ec8d2ec
4
+ data.tar.gz: feb94667bbb5777cfc467db6d987f7337fa8da5b7badd09ce59a88a964f71f26
5
5
  SHA512:
6
- metadata.gz: bca4330c17aa8d6d8f6563ebc52dae02d9029fb01580b9294ffd5d20822768f95f5c41a0c5bdd733d9f6481632f44a754ce67079cf843e9cf3c58ada9f9fbb39
7
- data.tar.gz: 3c24bf5fa9af05b1ef48f38ff20519466c5f63a47a1acbb5bf788d7e197c8af17759581c52443b8650c8ff4f3680bbd48aee3a8b224623e6097176aa284c5a11
6
+ metadata.gz: 1f095fb1ba849215724ad17e6f1ad27af8cc67674756022f2d4764109ce2231a8b71696b75f8bbb9562bc6754de4daeffae8a5d817588c918f9283d745ce01e8
7
+ data.tar.gz: ad06ba71f618763fca9712d33c298466b586fe84db6f9fe941a3582612a4de930e1e2892cc0a7a3197ff8d6f93bbf0018aa76e0caf47051568debba2f6dc1b7b
data/README.md CHANGED
@@ -43,7 +43,7 @@ rails g outbox:install
43
43
 
44
44
  ### Outbox/inbox items creation
45
45
 
46
- An ActiveRecord model can be generated for the outbox/ inbox item like this:
46
+ An ActiveRecord model can be generated for the outbox/inbox item like this:
47
47
 
48
48
  ```shell
49
49
  rails g outbox:item MaybeNamespaced::SomeOutboxItem --kind outbox
@@ -85,6 +85,39 @@ transaction do
85
85
  end
86
86
  ```
87
87
 
88
+ To create multiple Outbox items in batch, you should call the Interactor with the Item Model Class and batch attributes, each item should have same list of keys. Each item should have `event_key` element, it will be the Partitioning Key.
89
+
90
+ ```ruby
91
+ transaction do
92
+ some_record.save!
93
+ another_record.save!
94
+
95
+ result = Sbmt::Outbox::CreateOutboxBatch.call(
96
+ MyOutboxItem,
97
+ batch_attributes: [
98
+ {
99
+ event_key: some_record.id,
100
+ payload: some_record.generate_payload,
101
+ options: {
102
+ key: some_record.id, # optional, may be used when producing to a Kafka topic
103
+ headers: {'FOO_BAR' => 'baz'} # optional, you can add custom headers
104
+ }
105
+ },
106
+ {
107
+ event_key: another_record.id,
108
+ payload: another_record.generate_payload,
109
+ options: {
110
+ key: another_record.id, # optional, may be used when producing to a Kafka topic
111
+ headers: {'FOO_BAR' => 'baz'} # optional, you can add custom headers
112
+ }
113
+ }
114
+ ]
115
+ )
116
+
117
+ raise result.failure unless result.success?
118
+ end
119
+ ```
120
+
88
121
  ## Monitoring
89
122
 
90
123
  We use [Yabeda](https://github.com/yabeda-rb/yabeda) to collect [all kind of metrics](./config/initializers/yabeda.rb).
@@ -99,7 +132,7 @@ Example of a Grafana dashboard that you can import [from a file](./examples/graf
99
132
 
100
133
  ### `Outboxfile`
101
134
 
102
- First of all you shoudl create an `Outboxfile` at the root of your application with the following code:
135
+ First of all you should create an `Outboxfile` at the root of your application with the following code:
103
136
 
104
137
  ```ruby
105
138
  # frozen_string_literal: true
@@ -135,8 +168,8 @@ Rails.application.config.outbox.tap do |config|
135
168
  pc.retryable_items_batch_size = 100
136
169
 
137
170
  # poll tactic: default is optimal for most cases: rate limit + redis job-queue size threshold
138
- # poll tactic: aggressive is for high-intencity data: without rate limits + redis job-queue size threshold
139
- # poll tactic: low-priority is for low-intencity data: rate limits + redis job-queue size threshold + + redis job-queue lag threshold
171
+ # poll tactic: aggressive is for high-intensity data: without rate limits + redis job-queue size threshold
172
+ # poll tactic: low-priority is for low-intensity data: rate limits + redis job-queue size threshold + + redis job-queue lag threshold
140
173
  pc.tactic = "default"
141
174
  # number of batches that one thread will process per rate interval
142
175
  pc.rate_limit = 60
@@ -257,7 +290,7 @@ production:
257
290
  bucket_size: 256
258
291
  ```
259
292
  __CAUTION__:
260
- - ⚠️ If this option is enabled and an error occurs while processing a message in a bucket,
293
+ - ⚠️ If this option is enabled and an error occurs while processing a message in a bucket,
261
294
  subsequent messages in that bucket won't be processed until the current message is either skipped or successfully processed
262
295
  - ⚠️ Cannot use `retry_strategies` and the `strict_order` option at the same time
263
296
 
@@ -435,6 +468,9 @@ Rails.application.config.outbox.tap do |config|
435
468
  config.create_item_middlewares.push(
436
469
  'MyCreateItemMiddleware'
437
470
  )
471
+ config.create_batch_middlewares.push(
472
+ 'MyCreateBatchMiddleware'
473
+ )
438
474
  end
439
475
 
440
476
  # my_create_item_middleware.rb
@@ -445,6 +481,15 @@ class MyCreateItemMiddleware
445
481
  # your code
446
482
  end
447
483
  end
484
+
485
+ # my_create_batch_middleware.rb
486
+ class MyCreateBatchMiddleware
487
+ def call(item_class, batch_attributes)
488
+ # your code
489
+ yield
490
+ # your code
491
+ end
492
+ end
448
493
  ```
449
494
 
450
495
  #### Server middlewares
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sbmt/outbox/metrics/utils"
4
+
5
+ # Provides ability to insert records in batches.
6
+ # It allows to have different headers, event_key and partition_by between set of attributes,
7
+ # but for predictability of metrics results better to have same headers across items
8
+
9
+ module Sbmt
10
+ module Outbox
11
+ class CreateOutboxBatch < Outbox::DryInteractor
12
+ param :item_class, reader: :private
13
+ option :batch_attributes, reader: :private
14
+
15
+ delegate :box_type, :box_name, :owner, to: :item_class
16
+ delegate :create_batch_middlewares, to: "Sbmt::Outbox"
17
+
18
+ def call
19
+ middlewares = Middleware::Builder.new(create_batch_middlewares)
20
+ middlewares.call(item_class, batch_attributes) do
21
+ attributes_to_insert = batch_attributes.map do |attributes|
22
+ event_key = attributes[:event_key]
23
+ partition_by = attributes.delete(:partition_by) || event_key
24
+
25
+ return Failure(:missing_partition_by) unless partition_by
26
+ return Failure(:missing_event_key) unless event_key
27
+
28
+ # to get default values for some attributes, including uuid
29
+ record = item_class.new(attributes)
30
+
31
+ res = item_class.config.partition_strategy
32
+ .new(partition_by, item_class.config.bucket_size)
33
+ .call
34
+ record.bucket = res.value! if res.success?
35
+
36
+ # those 2 lines needed for rails 6, as it does not set timestamps
37
+ record.created_at ||= Time.zone.now
38
+ record.updated_at ||= record.created_at
39
+
40
+ record.attributes.reject { |_, value| value.nil? }
41
+ end
42
+
43
+ inserted_items = item_class.insert_all(attributes_to_insert, returning: [:id, :bucket])
44
+ inserted_items.rows.each do |(record_id, bucket)|
45
+ partition = item_class.bucket_partitions.fetch(bucket)
46
+ track_last_stored_id(record_id, partition)
47
+ track_counter(partition)
48
+ end
49
+
50
+ Success(inserted_items.rows.map(&:first))
51
+ rescue => e
52
+ Failure(e.message)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def track_last_stored_id(item_id, partition)
59
+ Yabeda
60
+ .outbox
61
+ .last_stored_event_id
62
+ .set({type: box_type, name: Sbmt::Outbox::Metrics::Utils.metric_safe(box_name), owner: owner, partition: partition}, item_id)
63
+ end
64
+
65
+ def track_counter(partition)
66
+ Yabeda
67
+ .outbox
68
+ .created_counter
69
+ .increment({type: box_type, name: Sbmt::Outbox::Metrics::Utils.metric_safe(box_name), owner: owner, partition: partition}, by: 1)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -93,7 +93,7 @@ module Sbmt
93
93
  track_discarded(item)
94
94
  Failure(:discard_item)
95
95
  else
96
- track_failed("retry stratagy returned unknown failure: #{result.failure}")
96
+ track_failed("retry strategy returned unknown failure: #{result.failure}")
97
97
  Failure(:retry_strategy_failure)
98
98
  end
99
99
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ if ActiveRecord.version >= Gem::Version.new("7.0.0")
4
+ Rails.application.config.active_record.tap do |config|
5
+ config.query_log_tags << {
6
+ box_name: ->(context) { context[:box_item]&.class&.box_name },
7
+ box_item_id: ->(context) { context[:box_item]&.uuid }
8
+ }
9
+ end
10
+ end
@@ -63,11 +63,17 @@ module Sbmt
63
63
  c.batch_process_middlewares = []
64
64
  c.item_process_middlewares = []
65
65
  c.create_item_middlewares = []
66
+ c.create_batch_middlewares = []
66
67
 
67
68
  if defined?(::Sentry)
68
69
  c.batch_process_middlewares.push("Sbmt::Outbox::Middleware::Sentry::TracingBatchProcessMiddleware")
69
70
  c.item_process_middlewares.push("Sbmt::Outbox::Middleware::Sentry::TracingItemProcessMiddleware")
70
71
  end
72
+
73
+ if defined?(ActiveSupport::ExecutionContext)
74
+ require_relative "middleware/execution_context/context_item_process_middleware"
75
+ c.item_process_middlewares.push("Sbmt::Outbox::Middleware::ExecutionContext::ContextItemProcessMiddleware")
76
+ end
71
77
  end
72
78
 
73
79
  rake_tasks do
@@ -5,6 +5,7 @@ require "opentelemetry-common"
5
5
  require "opentelemetry-instrumentation-base"
6
6
 
7
7
  require_relative "../middleware/open_telemetry/tracing_create_item_middleware"
8
+ require_relative "../middleware/open_telemetry/tracing_create_batch_middleware"
8
9
  require_relative "../middleware/open_telemetry/tracing_item_process_middleware"
9
10
 
10
11
  module Sbmt
@@ -15,6 +16,7 @@ module Sbmt
15
16
  require_dependencies
16
17
 
17
18
  ::Sbmt::Outbox.config.create_item_middlewares.push("Sbmt::Outbox::Middleware::OpenTelemetry::TracingCreateItemMiddleware")
19
+ ::Sbmt::Outbox.config.create_batch_middlewares.push("Sbmt::Outbox::Middleware::OpenTelemetry::TracingCreateBatchMiddleware")
18
20
  ::Sbmt::Outbox.config.item_process_middlewares.push("Sbmt::Outbox::Middleware::OpenTelemetry::TracingItemProcessMiddleware")
19
21
  end
20
22
 
@@ -26,6 +28,7 @@ module Sbmt
26
28
 
27
29
  def require_dependencies
28
30
  require_relative "../middleware/open_telemetry/tracing_create_item_middleware"
31
+ require_relative "../middleware/open_telemetry/tracing_create_batch_middleware"
29
32
  require_relative "../middleware/open_telemetry/tracing_item_process_middleware"
30
33
  end
31
34
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sbmt/outbox/middleware/execution_context/context_item_process_middleware"
4
+
5
+ module Sbmt
6
+ module Outbox
7
+ module Middleware
8
+ module ExecutionContext
9
+ class ContextItemProcessMiddleware
10
+ def call(item)
11
+ ActiveSupport::ExecutionContext[:box_item] = item
12
+
13
+ yield
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Outbox
5
+ module Middleware
6
+ module OpenTelemetry
7
+ class TracingCreateBatchMiddleware
8
+ def call(item_class, batch_attributes)
9
+ return yield unless defined?(::OpenTelemetry)
10
+
11
+ span_attributes = {
12
+ "messaging.system" => "outbox",
13
+ "messaging.outbox.box_type" => item_class.box_type.to_s,
14
+ "messaging.outbox.box_name" => item_class.box_name,
15
+ "messaging.outbox.owner" => item_class.owner,
16
+ "messaging.destination" => item_class.name,
17
+ "messaging.destination_kind" => "database"
18
+ }
19
+
20
+ tracer.in_span(span_name(item_class), attributes: span_attributes.compact, kind: :producer) do
21
+ batch_attributes.each do |item_attributes|
22
+ options = item_attributes[:options] ||= {}
23
+ headers = options[:headers] || options["headers"] || {}
24
+ ::OpenTelemetry.propagation.inject(headers)
25
+ end
26
+
27
+ yield
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def tracer
34
+ ::Sbmt::Outbox::Instrumentation::OpenTelemetryLoader.instance.tracer
35
+ end
36
+
37
+ def span_name(item_class)
38
+ "#{item_class.box_type}/#{item_class.box_name} create batch"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sbmt
4
4
  module Outbox
5
- VERSION = "6.6.0"
5
+ VERSION = "6.8.0"
6
6
  end
7
7
  end
data/lib/sbmt/outbox.rb CHANGED
@@ -166,5 +166,9 @@ module Sbmt
166
166
  def create_item_middlewares
167
167
  @create_item_middlewares ||= config.create_item_middlewares.map(&:constantize)
168
168
  end
169
+
170
+ def create_batch_middlewares
171
+ @create_batch_middlewares ||= config.create_batch_middlewares.map(&:constantize)
172
+ end
169
173
  end
170
174
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sbmt-outbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.6.0
4
+ version: 6.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sbermarket Ruby-Platform Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-06-20 00:00:00.000000000 Z
11
+ date: 2024-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool
@@ -519,6 +519,7 @@ files:
519
519
  - app/controllers/sbmt/outbox/root_controller.rb
520
520
  - app/interactors/sbmt/outbox/base_create_item.rb
521
521
  - app/interactors/sbmt/outbox/create_inbox_item.rb
522
+ - app/interactors/sbmt/outbox/create_outbox_batch.rb
522
523
  - app/interactors/sbmt/outbox/create_outbox_item.rb
523
524
  - app/interactors/sbmt/outbox/dry_interactor.rb
524
525
  - app/interactors/sbmt/outbox/partition_strategies/hash_partitioning.rb
@@ -543,6 +544,7 @@ files:
543
544
  - app/models/sbmt/outbox/outbox_item.rb
544
545
  - app/models/sbmt/outbox/outbox_item_config.rb
545
546
  - app/views/sbmt/outbox/root/index.erb
547
+ - config/initializers/active_record.rb
546
548
  - config/initializers/schked.rb
547
549
  - config/initializers/yabeda.rb
548
550
  - config/routes.rb
@@ -581,6 +583,8 @@ files:
581
583
  - lib/sbmt/outbox/logger.rb
582
584
  - lib/sbmt/outbox/metrics/utils.rb
583
585
  - lib/sbmt/outbox/middleware/builder.rb
586
+ - lib/sbmt/outbox/middleware/execution_context/context_item_process_middleware.rb
587
+ - lib/sbmt/outbox/middleware/open_telemetry/tracing_create_batch_middleware.rb
584
588
  - lib/sbmt/outbox/middleware/open_telemetry/tracing_create_item_middleware.rb
585
589
  - lib/sbmt/outbox/middleware/open_telemetry/tracing_item_process_middleware.rb
586
590
  - lib/sbmt/outbox/middleware/runner.rb
@@ -636,7 +640,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
636
640
  - !ruby/object:Gem::Version
637
641
  version: '0'
638
642
  requirements: []
639
- rubygems_version: 3.1.6
643
+ rubygems_version: 3.5.18
640
644
  signing_key:
641
645
  specification_version: 4
642
646
  summary: Outbox service