sbmt-outbox 5.0.4 → 6.0.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 +56 -7
- data/app/interactors/sbmt/outbox/process_item.rb +2 -1
- data/app/interactors/sbmt/outbox/retry_strategies/base.rb +15 -0
- data/app/interactors/sbmt/outbox/retry_strategies/compacted_log.rb +2 -32
- data/app/interactors/sbmt/outbox/retry_strategies/exponential_backoff.rb +3 -5
- data/app/interactors/sbmt/outbox/retry_strategies/latest_available.rb +39 -0
- data/app/interactors/sbmt/outbox/retry_strategies/no_delay.rb +13 -0
- data/app/models/sbmt/outbox/base_item.rb +9 -8
- data/app/models/sbmt/outbox/base_item_config.rb +11 -2
- data/config/initializers/yabeda.rb +32 -5
- data/lib/generators/helpers/migration.rb +2 -2
- data/lib/sbmt/outbox/cli.rb +50 -7
- data/lib/sbmt/outbox/engine.rb +25 -0
- data/lib/sbmt/outbox/logger.rb +6 -0
- data/lib/sbmt/outbox/v1/thread_pool.rb +110 -0
- data/lib/sbmt/outbox/v1/throttler.rb +54 -0
- data/lib/sbmt/outbox/v1/worker.rb +231 -0
- data/lib/sbmt/outbox/v2/box_processor.rb +148 -0
- data/lib/sbmt/outbox/v2/poll_throttler/base.rb +43 -0
- data/lib/sbmt/outbox/v2/poll_throttler/composite.rb +42 -0
- data/lib/sbmt/outbox/v2/poll_throttler/fixed_delay.rb +28 -0
- data/lib/sbmt/outbox/v2/poll_throttler/noop.rb +17 -0
- data/lib/sbmt/outbox/v2/poll_throttler/rate_limited.rb +24 -0
- data/lib/sbmt/outbox/v2/poll_throttler/redis_queue_size.rb +46 -0
- data/lib/sbmt/outbox/v2/poll_throttler/redis_queue_time_lag.rb +45 -0
- data/lib/sbmt/outbox/v2/poll_throttler.rb +49 -0
- data/lib/sbmt/outbox/v2/poller.rb +180 -0
- data/lib/sbmt/outbox/v2/processor.rb +101 -0
- data/lib/sbmt/outbox/v2/redis_job.rb +42 -0
- data/lib/sbmt/outbox/v2/tasks/base.rb +48 -0
- data/lib/sbmt/outbox/v2/tasks/default.rb +17 -0
- data/lib/sbmt/outbox/v2/tasks/poll.rb +34 -0
- data/lib/sbmt/outbox/v2/tasks/process.rb +31 -0
- data/lib/sbmt/outbox/v2/thread_pool.rb +152 -0
- data/lib/sbmt/outbox/v2/throttler.rb +13 -0
- data/lib/sbmt/outbox/v2/worker.rb +52 -0
- data/lib/sbmt/outbox/version.rb +1 -1
- data/lib/sbmt/outbox.rb +16 -2
- metadata +40 -4
- data/lib/sbmt/outbox/thread_pool.rb +0 -108
- data/lib/sbmt/outbox/throttler.rb +0 -52
- data/lib/sbmt/outbox/worker.rb +0 -233
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 818de79692d37138c630bf2fce625a54becf3a8fa71eafe3c5435d573dbcf665
|
4
|
+
data.tar.gz: 2516c414d14a60851d53fd9f821845179ac12cfb88176a18c1f60d8ba8e38bc9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e1c0f1f55ef75e5c73352f0789dc51559656f5868a64f2ca8b0c97c10b2865cd37189dc6b3a5d0346673251800ed1c652df951734a5fe77029ca87dbb0fc1a3
|
7
|
+
data.tar.gz: f0aba9307dcfac3c8e330f1a8a1e40e33439a624c4c40c34ea9223c53c25acff2540ad03c72b00a71c1e963885ecee4e6befc5e5e94d4d78e13ecf6acac4c69e
|
data/README.md
CHANGED
@@ -117,8 +117,8 @@ create_table :my_outbox_items do |t|
|
|
117
117
|
end
|
118
118
|
|
119
119
|
add_index :my_outbox_items, :uuid, unique: true
|
120
|
-
add_index :my_outbox_items, [:status, :bucket]
|
121
|
-
add_index :my_outbox_items, [:event_name, :event_key]
|
120
|
+
add_index :my_outbox_items, [:status, :bucket, :errors_count]
|
121
|
+
add_index :my_outbox_items, [:event_name, :event_key, :id]
|
122
122
|
add_index :my_outbox_items, :created_at
|
123
123
|
```
|
124
124
|
|
@@ -222,13 +222,51 @@ Rails.application.config.outbox.tap do |config|
|
|
222
222
|
x[:batch_size] = 200
|
223
223
|
end
|
224
224
|
|
225
|
-
# optional
|
225
|
+
# optional (worker v1: DEPRECATED)
|
226
226
|
config.worker.tap do |worker|
|
227
227
|
# number of batches that one thread will process per rate interval
|
228
228
|
worker[:rate_limit] = 10
|
229
229
|
# rate interval in seconds
|
230
230
|
worker[:rate_interval] = 60
|
231
231
|
end
|
232
|
+
|
233
|
+
# optional (worker v2: default)
|
234
|
+
c.poller = ActiveSupport::OrderedOptions.new.tap do |pc|
|
235
|
+
# max parallel threads (per box-item, globally)
|
236
|
+
pc.concurrency = 6
|
237
|
+
# max threads count (per worker process)
|
238
|
+
pc.threads_count = 1
|
239
|
+
# maximum processing time of the batch, after which the batch will be considered hung and processing will be aborted
|
240
|
+
pc.general_timeout = 60
|
241
|
+
# poll buffer consists of regular items (errors_count = 0, i.e. without any processing errors) and retryable items (errors_count > 0)
|
242
|
+
# max poll buffer size = regular_items_batch_size + retryable_items_batch_size
|
243
|
+
pc.regular_items_batch_size = 200
|
244
|
+
pc.retryable_items_batch_size = 100
|
245
|
+
|
246
|
+
# poll tactic: default is optimal for most cases: rate limit + redis job-queue size threshold
|
247
|
+
# poll tactic: aggressive is for high-intencity data: without rate limits + redis job-queue size threshold
|
248
|
+
# poll tactic: low-priority is for low-intencity data: rate limits + redis job-queue size threshold + + redis job-queue lag threshold
|
249
|
+
pc.tactic = "default"
|
250
|
+
# number of batches that one thread will process per rate interval
|
251
|
+
pc.rate_limit = 60
|
252
|
+
# rate interval in seconds
|
253
|
+
pc.rate_interval = 60
|
254
|
+
# mix / max redis job queue thresholds per box-item for default / aggressive / low-priority poll tactics
|
255
|
+
pc.min_queue_size = 10
|
256
|
+
pc.max_queue_size = 100
|
257
|
+
# min redis job queue time lag threshold per box-item for low-priority poll tactic (in seconds)
|
258
|
+
pc.min_queue_timelag = 5
|
259
|
+
# throttling delay for default / aggressive / low-priority poll tactics (in seconds)
|
260
|
+
pc.queue_delay = 0.1
|
261
|
+
end
|
262
|
+
c.processor = ActiveSupport::OrderedOptions.new.tap do |pc|
|
263
|
+
# max threads count (per worker process)
|
264
|
+
pc.threads_count = 4
|
265
|
+
# maximum processing time of the batch, after which the batch will be considered hung and processing will be aborted
|
266
|
+
pc.general_timeout = 120
|
267
|
+
# BRPOP delay (in seconds) for polling redis job queue per box-item
|
268
|
+
pc.brpop_delay = 2
|
269
|
+
end
|
232
270
|
end
|
233
271
|
```
|
234
272
|
|
@@ -292,7 +330,7 @@ outbox_items:
|
|
292
330
|
- exponential_backoff
|
293
331
|
```
|
294
332
|
|
295
|
-
####
|
333
|
+
#### Latest available
|
296
334
|
|
297
335
|
This strategy ensures idempotency. In short, if a message fails and a later message with the same event_key has already been delivered, then you most likely do not want to re-deliver the first one when it is retried.
|
298
336
|
|
@@ -303,10 +341,10 @@ outbox_items:
|
|
303
341
|
...
|
304
342
|
retry_strategies:
|
305
343
|
- exponential_backoff
|
306
|
-
-
|
344
|
+
- latest_available
|
307
345
|
```
|
308
346
|
|
309
|
-
The exponential backoff strategy should be used in conjunction with the
|
347
|
+
The exponential backoff strategy should be used in conjunction with the latest available strategy, and it should come last to minimize the number of database queries.
|
310
348
|
|
311
349
|
### Partition strategies
|
312
350
|
|
@@ -428,13 +466,24 @@ end
|
|
428
466
|
|
429
467
|
The gem is optionally integrated with OpenTelemetry. If your main application has `opentelemetry-*` gems, the tracing will be configured automatically.
|
430
468
|
|
431
|
-
## CLI Arguments
|
469
|
+
## CLI Arguments (v1: DEPRECATED)
|
432
470
|
|
433
471
|
| Key | Description |
|
434
472
|
|-----------------------|---------------------------------------------------------------------------|
|
435
473
|
| `--boxes or -b` | Outbox/Inbox processors to start` |
|
436
474
|
| `--concurrency or -c` | Number of threads. Default 10. |
|
437
475
|
|
476
|
+
## CLI Arguments (v2: default)
|
477
|
+
|
478
|
+
| Key | Description |
|
479
|
+
|----------------------------|----------------------------------------------------------------------|
|
480
|
+
| `--boxes or -b` | Outbox/Inbox processors to start` |
|
481
|
+
| `--concurrency or -c` | Number of process threads. Default 4. |
|
482
|
+
| `--poll-concurrency or -p` | Number of poller partitions. Default 6. |
|
483
|
+
| `--poll-threads or -n` | Number of poll threads. Default 1. |
|
484
|
+
| `--poll-tactic or -t` | Poll tactic. Default "default". |
|
485
|
+
| `--worker-version or -w` | Worker version. Default 2. |
|
486
|
+
|
438
487
|
## Development & Test
|
439
488
|
|
440
489
|
### Installation
|
@@ -5,6 +5,7 @@ module Sbmt
|
|
5
5
|
class ProcessItem < Sbmt::Outbox::DryInteractor
|
6
6
|
param :item_class, reader: :private
|
7
7
|
param :item_id, reader: :private
|
8
|
+
option :worker_version, reader: :private, optional: true, default: -> { 1 }
|
8
9
|
|
9
10
|
METRICS_COUNTERS = %i[error_counter retry_counter sent_counter fetch_error_counter discarded_counter].freeze
|
10
11
|
|
@@ -254,7 +255,7 @@ module Sbmt
|
|
254
255
|
end
|
255
256
|
|
256
257
|
def labels_for(item)
|
257
|
-
{type: box_type, name: box_name, owner: owner, partition: item&.partition}
|
258
|
+
{worker_version: worker_version, type: box_type, name: box_name, owner: owner, partition: item&.partition}
|
258
259
|
end
|
259
260
|
|
260
261
|
def counters
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Outbox
|
5
|
+
module RetryStrategies
|
6
|
+
class Base < Outbox::DryInteractor
|
7
|
+
param :item
|
8
|
+
|
9
|
+
def call
|
10
|
+
raise NotImplementedError, "Implement #call for Sbmt::Outbox::RetryStrategies::Base"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -3,38 +3,8 @@
|
|
3
3
|
module Sbmt
|
4
4
|
module Outbox
|
5
5
|
module RetryStrategies
|
6
|
-
class CompactedLog <
|
7
|
-
|
8
|
-
|
9
|
-
def call
|
10
|
-
unless outbox_item.has_attribute?(:event_key)
|
11
|
-
return Failure(:missing_event_key)
|
12
|
-
end
|
13
|
-
|
14
|
-
if outbox_item.event_key.nil?
|
15
|
-
return Failure(:empty_event_key)
|
16
|
-
end
|
17
|
-
|
18
|
-
if delivered_later?
|
19
|
-
Failure(:discard_item)
|
20
|
-
else
|
21
|
-
Success()
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def delivered_later?
|
28
|
-
scope = outbox_item.class
|
29
|
-
.where("id > ?", outbox_item)
|
30
|
-
.where(event_key: outbox_item.event_key, status: Sbmt::Outbox::BaseItem.statuses[:delivered])
|
31
|
-
|
32
|
-
if outbox_item.has_attribute?(:event_name) && outbox_item.event_name.present?
|
33
|
-
scope = scope.where(event_name: outbox_item.event_name)
|
34
|
-
end
|
35
|
-
|
36
|
-
scope.exists?
|
37
|
-
end
|
6
|
+
class CompactedLog < LatestAvailable
|
7
|
+
# exists only as alias for backward compatibility
|
38
8
|
end
|
39
9
|
end
|
40
10
|
end
|
@@ -3,13 +3,11 @@
|
|
3
3
|
module Sbmt
|
4
4
|
module Outbox
|
5
5
|
module RetryStrategies
|
6
|
-
class ExponentialBackoff <
|
7
|
-
param :outbox_item
|
8
|
-
|
6
|
+
class ExponentialBackoff < Base
|
9
7
|
def call
|
10
|
-
delay = backoff(
|
8
|
+
delay = backoff(item.config).interval_at(item.errors_count - 1)
|
11
9
|
|
12
|
-
still_early =
|
10
|
+
still_early = item.processed_at + delay.seconds > Time.current
|
13
11
|
|
14
12
|
if still_early
|
15
13
|
Failure(:skip_processing)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Outbox
|
5
|
+
module RetryStrategies
|
6
|
+
class LatestAvailable < Base
|
7
|
+
def call
|
8
|
+
unless item.has_attribute?(:event_key)
|
9
|
+
return Failure(:missing_event_key)
|
10
|
+
end
|
11
|
+
|
12
|
+
if item.event_key.nil?
|
13
|
+
return Failure(:empty_event_key)
|
14
|
+
end
|
15
|
+
|
16
|
+
if delivered_later?
|
17
|
+
Failure(:discard_item)
|
18
|
+
else
|
19
|
+
Success()
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def delivered_later?
|
26
|
+
scope = item.class
|
27
|
+
.where("id > ?", item)
|
28
|
+
.where(event_key: item.event_key)
|
29
|
+
|
30
|
+
if item.has_attribute?(:event_name) && item.event_name.present?
|
31
|
+
scope = scope.where(event_name: item.event_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
scope.exists?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -20,15 +20,16 @@ module Sbmt
|
|
20
20
|
@config ||= lookup_config.new(box_name)
|
21
21
|
end
|
22
22
|
|
23
|
+
def calc_bucket_partitions(count)
|
24
|
+
(0...count).to_a
|
25
|
+
.each_with_object({}) do |x, m|
|
26
|
+
m[x] = (0...config.bucket_size).to_a
|
27
|
+
.select { |p| p % count == x }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
23
31
|
def partition_buckets
|
24
|
-
@partition_buckets ||=
|
25
|
-
(0...config.partition_size)
|
26
|
-
.to_a
|
27
|
-
.each_with_object({}) do |x, m|
|
28
|
-
m[x] = (0...config.bucket_size)
|
29
|
-
.to_a
|
30
|
-
.select { |p| p % config.partition_size == x }
|
31
|
-
end
|
32
|
+
@partition_buckets ||= calc_bucket_partitions(config.partition_size)
|
32
33
|
end
|
33
34
|
|
34
35
|
def bucket_partitions
|
@@ -23,7 +23,11 @@ module Sbmt
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def partition_size
|
26
|
-
@partition_size ||= (
|
26
|
+
@partition_size ||= (partition_size_raw || 1).to_i
|
27
|
+
end
|
28
|
+
|
29
|
+
def partition_size_raw
|
30
|
+
@partition_size_raw ||= options[:partition_size]
|
27
31
|
end
|
28
32
|
|
29
33
|
def retention
|
@@ -47,7 +51,12 @@ module Sbmt
|
|
47
51
|
end
|
48
52
|
|
49
53
|
def retry_strategies
|
50
|
-
@retry_strategies
|
54
|
+
return @retry_strategies if defined?(@retry_strategies)
|
55
|
+
|
56
|
+
configured_strategies = options[:retry_strategies]
|
57
|
+
strategies = configured_strategies.presence || %w[exponential_backoff latest_available]
|
58
|
+
|
59
|
+
@retry_strategies ||= Array.wrap(strategies).map do |str_name|
|
51
60
|
"Sbmt::Outbox::RetryStrategies::#{str_name.camelize}".constantize
|
52
61
|
end
|
53
62
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
Yabeda.configure do
|
4
4
|
# error_counter retry_counter sent_counter fetch_error_counter discarded_counter
|
5
5
|
group :outbox do
|
6
|
+
default_tag(:worker_version, 1)
|
7
|
+
|
6
8
|
counter :created_counter,
|
7
9
|
tags: %i[type name partition owner],
|
8
10
|
comment: "The total number of created messages"
|
@@ -44,28 +46,53 @@ Yabeda.configure do
|
|
44
46
|
end
|
45
47
|
|
46
48
|
group :box_worker do
|
49
|
+
default_tag(:worker_version, 1)
|
50
|
+
default_tag(:worker_name, "worker")
|
51
|
+
|
47
52
|
counter :job_counter,
|
48
|
-
tags: %i[type name partition
|
53
|
+
tags: %i[type name partition state],
|
49
54
|
comment: "The total number of processed jobs"
|
50
55
|
|
51
56
|
counter :job_timeout_counter,
|
52
|
-
tags: %i[type name partition_key
|
57
|
+
tags: %i[type name partition_key],
|
53
58
|
comment: "Requeue of a job that occurred while processing the batch"
|
54
59
|
|
55
60
|
counter :job_items_counter,
|
56
|
-
tags: %i[type name partition
|
61
|
+
tags: %i[type name partition],
|
57
62
|
comment: "The total number of processed items in jobs"
|
58
63
|
|
59
64
|
histogram :job_execution_runtime,
|
60
65
|
comment: "A histogram of the job execution time",
|
61
66
|
unit: :seconds,
|
62
|
-
tags: %i[type name partition
|
67
|
+
tags: %i[type name partition],
|
63
68
|
buckets: [0.5, 1, 2.5, 5, 10, 15, 30, 45, 60, 90, 120, 180, 240, 300, 600]
|
64
69
|
|
65
70
|
histogram :item_execution_runtime,
|
66
71
|
comment: "A histogram of the item execution time",
|
67
72
|
unit: :seconds,
|
68
|
-
tags: %i[type name partition
|
73
|
+
tags: %i[type name partition],
|
74
|
+
buckets: [0.5, 1, 2.5, 5, 10, 15, 20, 30, 45, 60, 90, 120, 180, 240, 300]
|
75
|
+
|
76
|
+
counter :batches_per_poll_counter,
|
77
|
+
tags: %i[type name partition],
|
78
|
+
comment: "The total number of poll batches per poll"
|
79
|
+
|
80
|
+
gauge :redis_job_queue_size,
|
81
|
+
tags: %i[type name partition],
|
82
|
+
comment: "The total size of redis job queue"
|
83
|
+
|
84
|
+
gauge :redis_job_queue_time_lag,
|
85
|
+
tags: %i[type name partition],
|
86
|
+
comment: "The total time lag of redis job queue"
|
87
|
+
|
88
|
+
counter :poll_throttling_counter,
|
89
|
+
tags: %i[type name partition throttler status],
|
90
|
+
comment: "The total number of poll throttlings"
|
91
|
+
|
92
|
+
histogram :poll_throttling_runtime,
|
93
|
+
comment: "A histogram of the poll throttling time",
|
94
|
+
unit: :seconds,
|
95
|
+
tags: %i[type name partition throttler],
|
69
96
|
buckets: [0.5, 1, 2.5, 5, 10, 15, 20, 30, 45, 60, 90, 120, 180, 240, 300]
|
70
97
|
end
|
71
98
|
end
|
@@ -44,8 +44,8 @@ module Outbox
|
|
44
44
|
end
|
45
45
|
|
46
46
|
add_index :#{table_name}, :uuid, unique: true
|
47
|
-
add_index :#{table_name}, [:status, :bucket]
|
48
|
-
add_index :#{table_name}, :event_key
|
47
|
+
add_index :#{table_name}, [:status, :bucket, :errors_count]
|
48
|
+
add_index :#{table_name}, [:event_key, :id]
|
49
49
|
add_index :#{table_name}, :created_at
|
50
50
|
RUBY
|
51
51
|
|
data/lib/sbmt/outbox/cli.rb
CHANGED
@@ -20,15 +20,47 @@ module Sbmt
|
|
20
20
|
option :concurrency,
|
21
21
|
aliases: "-c",
|
22
22
|
type: :numeric,
|
23
|
-
|
24
|
-
|
23
|
+
desc: "Number of threads (processor)"
|
24
|
+
option :poll_concurrency,
|
25
|
+
aliases: "-p",
|
26
|
+
type: :numeric,
|
27
|
+
desc: "Number of poller partitions"
|
28
|
+
option :poll_threads,
|
29
|
+
aliases: "-n",
|
30
|
+
type: :numeric,
|
31
|
+
desc: "Number of threads (poller)"
|
32
|
+
option :poll_tactic,
|
33
|
+
aliases: "-t",
|
34
|
+
type: :string,
|
35
|
+
desc: "Poll tactic: [default, low-priority, aggressive]"
|
36
|
+
option :worker_version,
|
37
|
+
aliases: "-w",
|
38
|
+
type: :numeric,
|
39
|
+
desc: "Worker version: [1 | 2]"
|
25
40
|
def start
|
26
41
|
load_environment
|
27
42
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
)
|
43
|
+
version = options[:worker_version] || Outbox.default_worker_version
|
44
|
+
|
45
|
+
boxes = format_boxes(options[:box])
|
46
|
+
check_deprecations!(boxes, version)
|
47
|
+
|
48
|
+
worker = if version == 1
|
49
|
+
Sbmt::Outbox::V1::Worker.new(
|
50
|
+
boxes: boxes,
|
51
|
+
concurrency: options[:concurrency] || 10
|
52
|
+
)
|
53
|
+
elsif version == 2
|
54
|
+
Sbmt::Outbox::V2::Worker.new(
|
55
|
+
boxes: boxes,
|
56
|
+
poll_tactic: options[:poll_tactic],
|
57
|
+
poller_threads_count: options[:poll_threads],
|
58
|
+
poller_partitions_count: options[:poll_concurrency],
|
59
|
+
processor_concurrency: options[:concurrency] || 4
|
60
|
+
)
|
61
|
+
else
|
62
|
+
raise "Worker version #{version} is invalid, available versions: 1|2"
|
63
|
+
end
|
32
64
|
|
33
65
|
Sbmt::Outbox.current_worker = worker
|
34
66
|
|
@@ -45,11 +77,22 @@ module Sbmt
|
|
45
77
|
|
46
78
|
private
|
47
79
|
|
80
|
+
def check_deprecations!(boxes, version)
|
81
|
+
return unless version == 2
|
82
|
+
|
83
|
+
boxes.each do |item_class|
|
84
|
+
next if item_class.config.partition_size_raw.blank?
|
85
|
+
|
86
|
+
raise "partition_size option is invalid and cannot be used with worker v2, please remove it from config/outbox.yml for #{item_class.name.underscore}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
48
90
|
def load_environment
|
49
91
|
load(lookup_outboxfile)
|
50
92
|
|
51
93
|
require "sbmt/outbox"
|
52
|
-
require "sbmt/outbox/worker"
|
94
|
+
require "sbmt/outbox/v1/worker"
|
95
|
+
require "sbmt/outbox/v2/worker"
|
53
96
|
end
|
54
97
|
|
55
98
|
def lookup_outboxfile
|
data/lib/sbmt/outbox/engine.rb
CHANGED
@@ -26,6 +26,31 @@ module Sbmt
|
|
26
26
|
c.rate_interval = 60
|
27
27
|
c.shuffle_jobs = true
|
28
28
|
end
|
29
|
+
c.default_worker_version = 2
|
30
|
+
|
31
|
+
# worker v2
|
32
|
+
c.poller = ActiveSupport::OrderedOptions.new.tap do |pc|
|
33
|
+
pc.concurrency = 6
|
34
|
+
pc.threads_count = 2
|
35
|
+
pc.general_timeout = 60
|
36
|
+
pc.regular_items_batch_size = 200
|
37
|
+
pc.retryable_items_batch_size = 100
|
38
|
+
|
39
|
+
pc.tactic = "default"
|
40
|
+
pc.rate_limit = 60
|
41
|
+
pc.rate_interval = 60
|
42
|
+
pc.min_queue_size = 10
|
43
|
+
pc.max_queue_size = 100
|
44
|
+
pc.min_queue_timelag = 5
|
45
|
+
pc.queue_delay = 0.1
|
46
|
+
end
|
47
|
+
c.processor = ActiveSupport::OrderedOptions.new.tap do |pc|
|
48
|
+
pc.threads_count = 4
|
49
|
+
pc.general_timeout = 120
|
50
|
+
pc.cutoff_timeout = 60
|
51
|
+
pc.brpop_delay = 2
|
52
|
+
end
|
53
|
+
|
29
54
|
c.database_switcher = "Sbmt::Outbox::DatabaseSwitcher"
|
30
55
|
c.batch_process_middlewares = []
|
31
56
|
c.item_process_middlewares = []
|
data/lib/sbmt/outbox/logger.rb
CHANGED
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sbmt/outbox/v1/throttler"
|
4
|
+
|
5
|
+
module Sbmt
|
6
|
+
module Outbox
|
7
|
+
module V1
|
8
|
+
class ThreadPool
|
9
|
+
BREAK = Object.new.freeze
|
10
|
+
SKIPPED = Object.new.freeze
|
11
|
+
PROCESSED = Object.new.freeze
|
12
|
+
|
13
|
+
def initialize(&block)
|
14
|
+
self.task_source = block
|
15
|
+
self.task_mutex = Mutex.new
|
16
|
+
self.stopped = true
|
17
|
+
end
|
18
|
+
|
19
|
+
def next_task
|
20
|
+
task_mutex.synchronize do
|
21
|
+
return if stopped
|
22
|
+
item = task_source.call
|
23
|
+
|
24
|
+
if item == BREAK
|
25
|
+
self.stopped = true
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
item
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def start(concurrency:)
|
34
|
+
self.stopped = false
|
35
|
+
result = run_threads(count: concurrency) do |item|
|
36
|
+
yield worker_number, item
|
37
|
+
end
|
38
|
+
|
39
|
+
raise result if result.is_a?(Exception)
|
40
|
+
nil
|
41
|
+
ensure
|
42
|
+
self.stopped = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def stop
|
46
|
+
self.stopped = true
|
47
|
+
end
|
48
|
+
|
49
|
+
def worker_number
|
50
|
+
Thread.current["thread_pool_worker_number:#{object_id}"]
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
attr_accessor :task_source, :task_mutex, :stopped
|
56
|
+
|
57
|
+
def run_threads(count:)
|
58
|
+
exception = nil
|
59
|
+
|
60
|
+
in_threads(count: count) do |worker_num|
|
61
|
+
self.worker_number = worker_num
|
62
|
+
# We don't want to start all threads at the same time
|
63
|
+
random_sleep = rand * (worker_num + 1)
|
64
|
+
|
65
|
+
throttler = Throttler.new(
|
66
|
+
limit: Outbox.config.worker.rate_limit,
|
67
|
+
interval: Outbox.config.worker.rate_interval + random_sleep
|
68
|
+
)
|
69
|
+
|
70
|
+
sleep(random_sleep)
|
71
|
+
|
72
|
+
last_result = nil
|
73
|
+
until exception
|
74
|
+
throttler.wait if last_result == PROCESSED
|
75
|
+
item = next_task
|
76
|
+
break unless item
|
77
|
+
|
78
|
+
begin
|
79
|
+
last_result = yield item
|
80
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
81
|
+
exception = e
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
exception
|
87
|
+
end
|
88
|
+
|
89
|
+
def in_threads(count:)
|
90
|
+
threads = []
|
91
|
+
|
92
|
+
Thread.handle_interrupt(Exception => :never) do
|
93
|
+
Thread.handle_interrupt(Exception => :immediate) do
|
94
|
+
count.times do |i|
|
95
|
+
threads << Thread.new { yield(i) }
|
96
|
+
end
|
97
|
+
threads.map(&:value)
|
98
|
+
end
|
99
|
+
ensure
|
100
|
+
threads.each(&:kill)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def worker_number=(num)
|
105
|
+
Thread.current["thread_pool_worker_number:#{object_id}"] = num
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|