sbmt-outbox 5.0.4 → 6.0.1
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 +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
|