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 +4 -4
- data/README.md +50 -5
- data/app/interactors/sbmt/outbox/create_outbox_batch.rb +73 -0
- data/app/interactors/sbmt/outbox/process_item.rb +1 -1
- data/config/initializers/active_record.rb +10 -0
- data/lib/sbmt/outbox/engine.rb +6 -0
- data/lib/sbmt/outbox/instrumentation/open_telemetry_loader.rb +3 -0
- data/lib/sbmt/outbox/middleware/execution_context/context_item_process_middleware.rb +19 -0
- data/lib/sbmt/outbox/middleware/open_telemetry/tracing_create_batch_middleware.rb +44 -0
- data/lib/sbmt/outbox/version.rb +1 -1
- data/lib/sbmt/outbox.rb +4 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7eeda1fb96153d344c166cc65ba9dd8cfcfdfb1402184258e37efff70ec8d2ec
|
4
|
+
data.tar.gz: feb94667bbb5777cfc467db6d987f7337fa8da5b7badd09ce59a88a964f71f26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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/
|
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
|
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-
|
139
|
-
# poll tactic: low-priority is for low-
|
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
|
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
|
data/lib/sbmt/outbox/engine.rb
CHANGED
@@ -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
|
data/lib/sbmt/outbox/version.rb
CHANGED
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.
|
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-
|
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.
|
643
|
+
rubygems_version: 3.5.18
|
640
644
|
signing_key:
|
641
645
|
specification_version: 4
|
642
646
|
summary: Outbox service
|