sbmt-outbox 5.0.4 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +45 -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 +109 -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
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redlock"
|
4
|
+
require "sbmt/outbox/v2/poller"
|
5
|
+
require "sbmt/outbox/v2/processor"
|
6
|
+
|
7
|
+
module Sbmt
|
8
|
+
module Outbox
|
9
|
+
module V2
|
10
|
+
class Worker
|
11
|
+
def initialize(boxes:, poll_tactic: nil, processor_concurrency: nil, poller_partitions_count: nil, poller_threads_count: nil)
|
12
|
+
@poller = Poller.new(boxes, throttler_tactic: poll_tactic, threads_count: poller_threads_count, partitions_count: poller_partitions_count)
|
13
|
+
@processor = Processor.new(boxes, threads_count: processor_concurrency)
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
start_async
|
18
|
+
|
19
|
+
loop do
|
20
|
+
sleep 0.1
|
21
|
+
break unless @poller.started && @processor.started
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def start_async
|
26
|
+
@poller.start
|
27
|
+
@processor.start
|
28
|
+
|
29
|
+
loop do
|
30
|
+
sleep(0.1)
|
31
|
+
break if ready?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop
|
36
|
+
@poller.stop
|
37
|
+
@processor.stop
|
38
|
+
end
|
39
|
+
|
40
|
+
def ready?
|
41
|
+
@poller.ready? && @processor.ready?
|
42
|
+
end
|
43
|
+
|
44
|
+
def alive?
|
45
|
+
return false unless ready?
|
46
|
+
|
47
|
+
@poller.alive?(@poller.lock_timeout) && @processor.alive?(@processor.lock_timeout)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/sbmt/outbox/version.rb
CHANGED
data/lib/sbmt/outbox.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails"
|
4
|
+
require "active_job"
|
5
|
+
require "active_record"
|
4
6
|
require "dry-initializer"
|
5
7
|
require "dry-monads"
|
6
8
|
require "dry/monads/do"
|
@@ -53,12 +55,24 @@ module Sbmt
|
|
53
55
|
@logger ||= Sbmt::Outbox::Logger.new
|
54
56
|
end
|
55
57
|
|
58
|
+
def poller_config
|
59
|
+
@poller_config ||= config.poller
|
60
|
+
end
|
61
|
+
|
62
|
+
def processor_config
|
63
|
+
@processor_config ||= config.processor
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_worker_version
|
67
|
+
@default_worker_version ||= config.default_worker_version&.to_i || 2
|
68
|
+
end
|
69
|
+
|
56
70
|
def active_record_base_class
|
57
|
-
@active_record_base_class ||= config.active_record_base_class.safe_constantize || ActiveRecord::Base
|
71
|
+
@active_record_base_class ||= config.active_record_base_class.safe_constantize || ::ActiveRecord::Base
|
58
72
|
end
|
59
73
|
|
60
74
|
def active_job_base_class
|
61
|
-
@active_job_base_class ||= config.active_job_base_class.safe_constantize || ActiveJob::Base
|
75
|
+
@active_job_base_class ||= config.active_job_base_class.safe_constantize || ::ActiveJob::Base
|
62
76
|
end
|
63
77
|
|
64
78
|
def error_tracker
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sbmt-outbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 6.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sbermarket Ruby-Platform Team
|
@@ -188,6 +188,20 @@ dependencies:
|
|
188
188
|
- - "~>"
|
189
189
|
- !ruby/object:Gem::Version
|
190
190
|
version: '0.5'
|
191
|
+
- !ruby/object:Gem::Dependency
|
192
|
+
name: ruby-limiter
|
193
|
+
requirement: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - "~>"
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '2.3'
|
198
|
+
type: :runtime
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: !ruby/object:Gem::Requirement
|
201
|
+
requirements:
|
202
|
+
- - "~>"
|
203
|
+
- !ruby/object:Gem::Version
|
204
|
+
version: '2.3'
|
191
205
|
- !ruby/object:Gem::Dependency
|
192
206
|
name: appraisal
|
193
207
|
requirement: !ruby/object:Gem::Requirement
|
@@ -506,8 +520,11 @@ files:
|
|
506
520
|
- app/interactors/sbmt/outbox/partition_strategies/hash_partitioning.rb
|
507
521
|
- app/interactors/sbmt/outbox/partition_strategies/number_partitioning.rb
|
508
522
|
- app/interactors/sbmt/outbox/process_item.rb
|
523
|
+
- app/interactors/sbmt/outbox/retry_strategies/base.rb
|
509
524
|
- app/interactors/sbmt/outbox/retry_strategies/compacted_log.rb
|
510
525
|
- app/interactors/sbmt/outbox/retry_strategies/exponential_backoff.rb
|
526
|
+
- app/interactors/sbmt/outbox/retry_strategies/latest_available.rb
|
527
|
+
- app/interactors/sbmt/outbox/retry_strategies/no_delay.rb
|
511
528
|
- app/jobs/sbmt/outbox/base_delete_stale_items_job.rb
|
512
529
|
- app/jobs/sbmt/outbox/delete_stale_inbox_items_job.rb
|
513
530
|
- app/jobs/sbmt/outbox/delete_stale_outbox_items_job.rb
|
@@ -563,10 +580,29 @@ files:
|
|
563
580
|
- lib/sbmt/outbox/redis_client_factory.rb
|
564
581
|
- lib/sbmt/outbox/tasks/delete_failed_items.rake
|
565
582
|
- lib/sbmt/outbox/tasks/retry_failed_items.rake
|
566
|
-
- lib/sbmt/outbox/thread_pool.rb
|
567
|
-
- lib/sbmt/outbox/throttler.rb
|
583
|
+
- lib/sbmt/outbox/v1/thread_pool.rb
|
584
|
+
- lib/sbmt/outbox/v1/throttler.rb
|
585
|
+
- lib/sbmt/outbox/v1/worker.rb
|
586
|
+
- lib/sbmt/outbox/v2/box_processor.rb
|
587
|
+
- lib/sbmt/outbox/v2/poll_throttler.rb
|
588
|
+
- lib/sbmt/outbox/v2/poll_throttler/base.rb
|
589
|
+
- lib/sbmt/outbox/v2/poll_throttler/composite.rb
|
590
|
+
- lib/sbmt/outbox/v2/poll_throttler/fixed_delay.rb
|
591
|
+
- lib/sbmt/outbox/v2/poll_throttler/noop.rb
|
592
|
+
- lib/sbmt/outbox/v2/poll_throttler/rate_limited.rb
|
593
|
+
- lib/sbmt/outbox/v2/poll_throttler/redis_queue_size.rb
|
594
|
+
- lib/sbmt/outbox/v2/poll_throttler/redis_queue_time_lag.rb
|
595
|
+
- lib/sbmt/outbox/v2/poller.rb
|
596
|
+
- lib/sbmt/outbox/v2/processor.rb
|
597
|
+
- lib/sbmt/outbox/v2/redis_job.rb
|
598
|
+
- lib/sbmt/outbox/v2/tasks/base.rb
|
599
|
+
- lib/sbmt/outbox/v2/tasks/default.rb
|
600
|
+
- lib/sbmt/outbox/v2/tasks/poll.rb
|
601
|
+
- lib/sbmt/outbox/v2/tasks/process.rb
|
602
|
+
- lib/sbmt/outbox/v2/thread_pool.rb
|
603
|
+
- lib/sbmt/outbox/v2/throttler.rb
|
604
|
+
- lib/sbmt/outbox/v2/worker.rb
|
568
605
|
- lib/sbmt/outbox/version.rb
|
569
|
-
- lib/sbmt/outbox/worker.rb
|
570
606
|
homepage: https://github.com/SberMarket-Tech/sbmt-outbox
|
571
607
|
licenses:
|
572
608
|
- MIT
|
@@ -1,108 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "sbmt/outbox/throttler"
|
4
|
-
|
5
|
-
module Sbmt
|
6
|
-
module Outbox
|
7
|
-
class ThreadPool
|
8
|
-
BREAK = Object.new.freeze
|
9
|
-
SKIPPED = Object.new.freeze
|
10
|
-
PROCESSED = Object.new.freeze
|
11
|
-
|
12
|
-
def initialize(&block)
|
13
|
-
self.task_source = block
|
14
|
-
self.task_mutex = Mutex.new
|
15
|
-
self.stopped = true
|
16
|
-
end
|
17
|
-
|
18
|
-
def next_task
|
19
|
-
task_mutex.synchronize do
|
20
|
-
return if stopped
|
21
|
-
item = task_source.call
|
22
|
-
|
23
|
-
if item == BREAK
|
24
|
-
self.stopped = true
|
25
|
-
return
|
26
|
-
end
|
27
|
-
|
28
|
-
item
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def start(concurrency:)
|
33
|
-
self.stopped = false
|
34
|
-
result = run_threads(count: concurrency) do |item|
|
35
|
-
yield worker_number, item
|
36
|
-
end
|
37
|
-
|
38
|
-
raise result if result.is_a?(Exception)
|
39
|
-
nil
|
40
|
-
ensure
|
41
|
-
self.stopped = true
|
42
|
-
end
|
43
|
-
|
44
|
-
def stop
|
45
|
-
self.stopped = true
|
46
|
-
end
|
47
|
-
|
48
|
-
def worker_number
|
49
|
-
Thread.current["thread_pool_worker_number:#{object_id}"]
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
attr_accessor :task_source, :task_mutex, :stopped
|
55
|
-
|
56
|
-
def run_threads(count:)
|
57
|
-
exception = nil
|
58
|
-
|
59
|
-
in_threads(count: count) do |worker_num|
|
60
|
-
self.worker_number = worker_num
|
61
|
-
# We don't want to start all threads at the same time
|
62
|
-
random_sleep = rand * (worker_num + 1)
|
63
|
-
|
64
|
-
throttler = Throttler.new(
|
65
|
-
limit: Outbox.config.worker.rate_limit,
|
66
|
-
interval: Outbox.config.worker.rate_interval + random_sleep
|
67
|
-
)
|
68
|
-
|
69
|
-
sleep(random_sleep)
|
70
|
-
|
71
|
-
last_result = nil
|
72
|
-
until exception
|
73
|
-
throttler.wait if last_result == PROCESSED
|
74
|
-
item = next_task
|
75
|
-
break unless item
|
76
|
-
|
77
|
-
begin
|
78
|
-
last_result = yield item
|
79
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
80
|
-
exception = e
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
exception
|
86
|
-
end
|
87
|
-
|
88
|
-
def in_threads(count:)
|
89
|
-
threads = []
|
90
|
-
|
91
|
-
Thread.handle_interrupt(Exception => :never) do
|
92
|
-
Thread.handle_interrupt(Exception => :immediate) do
|
93
|
-
count.times do |i|
|
94
|
-
threads << Thread.new { yield(i) }
|
95
|
-
end
|
96
|
-
threads.map(&:value)
|
97
|
-
end
|
98
|
-
ensure
|
99
|
-
threads.each(&:kill)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def worker_number=(num)
|
104
|
-
Thread.current["thread_pool_worker_number:#{object_id}"] = num
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Sbmt
|
4
|
-
module Outbox
|
5
|
-
# Based on https://github.com/Shopify/limiter/blob/master/lib/limiter/rate_queue.rb
|
6
|
-
# We cannot use that gem because we have to support Ruby 2.5,
|
7
|
-
# but Shopify's limiter requires minimum Ruby 2.6
|
8
|
-
class Throttler
|
9
|
-
def initialize(limit: nil, interval: nil)
|
10
|
-
@limit = limit
|
11
|
-
@interval = limit
|
12
|
-
@map = (0...@limit).map { |i| base_time + (gap * i) }
|
13
|
-
@index = 0
|
14
|
-
@mutex = Mutex.new
|
15
|
-
end
|
16
|
-
|
17
|
-
def wait
|
18
|
-
time = nil
|
19
|
-
|
20
|
-
@mutex.synchronize do
|
21
|
-
time = @map[@index]
|
22
|
-
|
23
|
-
sleep_until(time + @interval)
|
24
|
-
|
25
|
-
@map[@index] = now
|
26
|
-
@index = (@index + 1) % @limit
|
27
|
-
end
|
28
|
-
|
29
|
-
time
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def sleep_until(time)
|
35
|
-
period = time - now
|
36
|
-
sleep(period) if period > 0
|
37
|
-
end
|
38
|
-
|
39
|
-
def base_time
|
40
|
-
now - @interval
|
41
|
-
end
|
42
|
-
|
43
|
-
def gap
|
44
|
-
@interval.to_f / @limit.to_f
|
45
|
-
end
|
46
|
-
|
47
|
-
def now
|
48
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
data/lib/sbmt/outbox/worker.rb
DELETED
@@ -1,233 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "redlock"
|
4
|
-
require "sbmt/outbox/thread_pool"
|
5
|
-
|
6
|
-
module Sbmt
|
7
|
-
module Outbox
|
8
|
-
class Worker
|
9
|
-
Job = Struct.new(
|
10
|
-
:item_class,
|
11
|
-
:partition,
|
12
|
-
:buckets,
|
13
|
-
:log_tags,
|
14
|
-
:yabeda_labels,
|
15
|
-
:resource_key,
|
16
|
-
:resource_path,
|
17
|
-
keyword_init: true
|
18
|
-
)
|
19
|
-
|
20
|
-
delegate :config,
|
21
|
-
:logger,
|
22
|
-
:batch_process_middlewares,
|
23
|
-
to: "Sbmt::Outbox"
|
24
|
-
delegate :stop, to: :thread_pool
|
25
|
-
delegate :general_timeout, :cutoff_timeout, :batch_size, to: "Sbmt::Outbox.config.process_items"
|
26
|
-
delegate :job_counter,
|
27
|
-
:job_execution_runtime,
|
28
|
-
:item_execution_runtime,
|
29
|
-
:job_items_counter,
|
30
|
-
:job_timeout_counter,
|
31
|
-
to: "Yabeda.box_worker"
|
32
|
-
|
33
|
-
def initialize(boxes:, concurrency: 10)
|
34
|
-
self.queue = Queue.new
|
35
|
-
build_jobs(boxes).each { |job| queue << job }
|
36
|
-
self.thread_pool = ThreadPool.new { queue.pop }
|
37
|
-
self.concurrency = [concurrency, queue.size].min
|
38
|
-
self.thread_workers = {}
|
39
|
-
init_redis
|
40
|
-
end
|
41
|
-
|
42
|
-
def start
|
43
|
-
raise "Outbox is already started" if started
|
44
|
-
self.started = true
|
45
|
-
self.thread_workers = {}
|
46
|
-
|
47
|
-
thread_pool.start(concurrency: concurrency) do |worker_number, job|
|
48
|
-
touch_thread_worker!
|
49
|
-
result = ThreadPool::PROCESSED
|
50
|
-
logger.with_tags(**job.log_tags.merge(worker: worker_number)) do
|
51
|
-
lock_manager.lock("#{job.resource_path}:lock", general_timeout * 1000) do |locked|
|
52
|
-
labels = job.yabeda_labels.merge(worker_number: worker_number)
|
53
|
-
|
54
|
-
if locked
|
55
|
-
job_execution_runtime.measure(labels) do
|
56
|
-
::Rails.application.executor.wrap do
|
57
|
-
safe_process_job(job, worker_number, labels)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
else
|
61
|
-
result = ThreadPool::SKIPPED
|
62
|
-
logger.log_info("Skip processing already locked #{job.resource_key}")
|
63
|
-
end
|
64
|
-
|
65
|
-
job_counter.increment(labels.merge(state: locked ? "processed" : "skipped"), by: 1)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
result
|
70
|
-
ensure
|
71
|
-
queue << job
|
72
|
-
end
|
73
|
-
rescue => e
|
74
|
-
Outbox.error_tracker.error(e)
|
75
|
-
raise
|
76
|
-
ensure
|
77
|
-
self.started = false
|
78
|
-
end
|
79
|
-
|
80
|
-
def ready?
|
81
|
-
started && thread_workers.any?
|
82
|
-
end
|
83
|
-
|
84
|
-
def alive?
|
85
|
-
return false unless started
|
86
|
-
|
87
|
-
deadline = Time.current - general_timeout
|
88
|
-
thread_workers.all? do |_worker_number, time|
|
89
|
-
deadline < time
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
private
|
94
|
-
|
95
|
-
attr_accessor :queue, :thread_pool, :concurrency, :lock_manager, :redis, :thread_workers, :started
|
96
|
-
|
97
|
-
def init_redis
|
98
|
-
self.redis = ConnectionPool::Wrapper.new(size: concurrency) { RedisClientFactory.build(config.redis) }
|
99
|
-
|
100
|
-
client = if Gem::Version.new(Redlock::VERSION) >= Gem::Version.new("2.0.0")
|
101
|
-
redis
|
102
|
-
else
|
103
|
-
ConnectionPool::Wrapper.new(size: concurrency) { Redis.new(config.redis) }
|
104
|
-
end
|
105
|
-
|
106
|
-
self.lock_manager = Redlock::Client.new([client], retry_count: 0)
|
107
|
-
end
|
108
|
-
|
109
|
-
def build_jobs(boxes)
|
110
|
-
res = boxes.map do |item_class|
|
111
|
-
partitions = (0...item_class.config.partition_size).to_a
|
112
|
-
partitions.map do |partition|
|
113
|
-
buckets = item_class.partition_buckets.fetch(partition)
|
114
|
-
resource_key = "#{item_class.box_name}/#{partition}"
|
115
|
-
|
116
|
-
Job.new(
|
117
|
-
item_class: item_class,
|
118
|
-
partition: partition,
|
119
|
-
buckets: buckets,
|
120
|
-
log_tags: {
|
121
|
-
box_type: item_class.box_type,
|
122
|
-
box_name: item_class.box_name,
|
123
|
-
box_partition: partition,
|
124
|
-
trace_id: nil
|
125
|
-
},
|
126
|
-
yabeda_labels: {
|
127
|
-
type: item_class.box_type,
|
128
|
-
name: item_class.box_name,
|
129
|
-
partition: partition
|
130
|
-
},
|
131
|
-
resource_key: resource_key,
|
132
|
-
resource_path: "sbmt/outbox/worker/#{resource_key}"
|
133
|
-
)
|
134
|
-
end
|
135
|
-
end.flatten
|
136
|
-
|
137
|
-
res.shuffle! if Outbox.config.worker.shuffle_jobs
|
138
|
-
res
|
139
|
-
end
|
140
|
-
|
141
|
-
def touch_thread_worker!
|
142
|
-
thread_workers[thread_pool.worker_number] = Time.current
|
143
|
-
end
|
144
|
-
|
145
|
-
def safe_process_job(job, worker_number, labels)
|
146
|
-
middlewares = Middleware::Builder.new(batch_process_middlewares)
|
147
|
-
|
148
|
-
middlewares.call(job) do
|
149
|
-
start_id ||= redis.call("GETDEL", "#{job.resource_path}:last_id").to_i + 1
|
150
|
-
logger.log_info("Start processing #{job.resource_key} from id #{start_id}")
|
151
|
-
process_job_with_timeouts(job, start_id, labels)
|
152
|
-
end
|
153
|
-
rescue => e
|
154
|
-
log_fatal(e, job, worker_number)
|
155
|
-
track_fatal(e, job, worker_number)
|
156
|
-
end
|
157
|
-
|
158
|
-
def process_job_with_timeouts(job, start_id, labels)
|
159
|
-
count = 0
|
160
|
-
last_id = nil
|
161
|
-
lock_timer = Cutoff.new(general_timeout)
|
162
|
-
requeue_timer = Cutoff.new(cutoff_timeout)
|
163
|
-
|
164
|
-
process_job(job, start_id, labels) do |item|
|
165
|
-
job_items_counter.increment(labels, by: 1)
|
166
|
-
last_id = item.id
|
167
|
-
count += 1
|
168
|
-
lock_timer.checkpoint!
|
169
|
-
requeue_timer.checkpoint!
|
170
|
-
end
|
171
|
-
|
172
|
-
logger.log_info("Finish processing #{job.resource_key} at id #{last_id}")
|
173
|
-
rescue Cutoff::CutoffExceededError
|
174
|
-
job_timeout_counter.increment(labels, by: 1)
|
175
|
-
|
176
|
-
msg = if lock_timer.exceeded?
|
177
|
-
"Lock timeout"
|
178
|
-
elsif requeue_timer.exceeded?
|
179
|
-
redis.call("SET", "#{job.resource_path}:last_id", last_id, "EX", general_timeout) if last_id
|
180
|
-
"Requeue timeout"
|
181
|
-
end
|
182
|
-
raise "Unknown timer has been timed out" unless msg
|
183
|
-
|
184
|
-
logger.log_info("#{msg} while processing #{job.resource_key} at id #{last_id}")
|
185
|
-
end
|
186
|
-
|
187
|
-
def process_job(job, start_id, labels)
|
188
|
-
Outbox.database_switcher.use_slave do
|
189
|
-
item_class = job.item_class
|
190
|
-
|
191
|
-
scope = item_class
|
192
|
-
.for_processing
|
193
|
-
.select(:id)
|
194
|
-
|
195
|
-
if item_class.has_attribute?(:bucket)
|
196
|
-
scope = scope.where(bucket: job.buckets)
|
197
|
-
elsif job.partition > 0
|
198
|
-
raise "Could not filter by partition #{job.resource_key}"
|
199
|
-
end
|
200
|
-
|
201
|
-
scope.find_each(start: start_id, batch_size: batch_size) do |item|
|
202
|
-
touch_thread_worker!
|
203
|
-
item_execution_runtime.measure(labels) do
|
204
|
-
Outbox.database_switcher.use_master do
|
205
|
-
ProcessItem.call(job.item_class, item.id)
|
206
|
-
end
|
207
|
-
yield item
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
def log_fatal(e, job, worker_number)
|
214
|
-
backtrace = e.backtrace.join("\n") if e.respond_to?(:backtrace)
|
215
|
-
|
216
|
-
logger.log_error(
|
217
|
-
"Failed processing #{job.resource_key} with error: #{e.class} #{e.message}",
|
218
|
-
backtrace: backtrace
|
219
|
-
)
|
220
|
-
end
|
221
|
-
|
222
|
-
def track_fatal(e, job, worker_number)
|
223
|
-
job_counter
|
224
|
-
.increment(
|
225
|
-
job.yabeda_labels.merge(worker_number: worker_number, state: "failed"),
|
226
|
-
by: 1
|
227
|
-
)
|
228
|
-
|
229
|
-
Outbox.error_tracker.error(e, **job.log_tags)
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
end
|