sidekiq-clutch 1.1.0 → 2.1.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: 34898643a173edd20ec4a28e2a2f01f15c05d9e074d67a72e9f06a56580f2367
4
- data.tar.gz: 4e39b5cd8a173520db812680c647ba8708f547614986bb8ad13be5d872377117
3
+ metadata.gz: d471e4ebb14bcae3bcbb03c855ab949b1029b9a2bbb0210927bc6f38b7f8906d
4
+ data.tar.gz: 5059ca6f29a0a3de77bef0060112b7bd448a64db5c18672a859fa91b70f1b7d7
5
5
  SHA512:
6
- metadata.gz: fcfd9af62fc2a84546862f12813ffed617eaeee69b10f0136529a70d4307bfb85616b1629223d50e909c967d98462dcf4b04ec0492c56f3dd5fd5ce43bda2946
7
- data.tar.gz: 699d22544e893ea1130e0bd4967888781d2ee1856b7a918a919eff639b4dcd40cade5651840ab22b95f9b43a27262078d37b30e32b0ff6aea3a06203d378e897
6
+ metadata.gz: 9fec12c8483a7f59a466db621b77cf123bd4bb09671181b397926062b4f1c2a058be992581bb1a8b931394921da372ce737b3e365aff3dd420fe2562b818efc2
7
+ data.tar.gz: a9fc7e8afac8d30c45fa15766cc26eacb524b4f324ded0d6dfda9d314270dabed274831523ed728c8ec08900f9d3f6e540505ff05593daed26badac7cef08c89
data/.circleci/config.yml CHANGED
@@ -7,5 +7,6 @@ jobs:
7
7
  - image: redis:3.2
8
8
  steps:
9
9
  - checkout
10
+ - run: gem install bundler
10
11
  - run: bundle install
11
12
  - run: bundle exec rspec
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ /*.gem
1
2
  /.bundle/
2
3
  .rspec_status
3
4
  Gemfile.lock
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ # 2.1.0 - Sep 10, 2021
2
+
3
+ * PERF: Don't pass step data in success callback to every batch
4
+
5
+ NOTE: This changes the structure of arguments passed to Batch callbacks, though we
6
+ also handle the old way of doing it (v2.0.2 and before) so it shouldn't affect
7
+ any in-progress batches you may have running.
8
+
9
+ # 2.0.2 - Sep 24, 2020
10
+
11
+ * PERF: Delete keys based on known values, instead of a glob
12
+ * PERF: Don't bother enqueing a `:complete` callback if no `on_failure` is specified
13
+
14
+ # 2.0.1 - Jun 1, 2020
15
+
16
+ * CHORE: Bump development dependency versions
17
+ * CHORE: Add .ruby-version file for easier development
18
+
19
+ # 2.0.0 - Feb 5, 2020
20
+
21
+ * BREAKING: Treat each parallel block as a distinct step
22
+
1
23
  # 1.1.0 - Feb 5, 2020
2
24
 
3
25
  * FEAT: use Sidekiq's wrapped option for improved logging
@@ -3,9 +3,6 @@ module Sidekiq
3
3
  class JobWrapper
4
4
  include Sidekiq::Worker
5
5
 
6
- # 22 days - how long a Sidekiq job can live with exponential backoff
7
- RESULT_KEY_EXPIRATION_DURATION = 22 * 24 * 60 * 60
8
-
9
6
  def perform(bid, job_class, args, last_result_key, current_result_key)
10
7
  job = Object.const_get(job_class).new
11
8
  assign_previous_results(job, last_result_key)
@@ -14,7 +11,7 @@ module Sidekiq
14
11
  Sidekiq.redis do |redis|
15
12
  redis.multi do |multi|
16
13
  multi.rpush(current_result_key, result.to_json)
17
- multi.expire(current_result_key, RESULT_KEY_EXPIRATION_DURATION)
14
+ multi.expire(current_result_key, TEMPORARY_KEY_EXPIRATION_DURATION)
18
15
  end
19
16
  end
20
17
  end
@@ -4,7 +4,6 @@ module Sidekiq
4
4
  def initialize(service)
5
5
  @service = service
6
6
  @jobs = []
7
- @result_key_prefix = SecureRandom.uuid
8
7
  @result_key_index = 0
9
8
  end
10
9
 
@@ -22,16 +21,25 @@ module Sidekiq
22
21
 
23
22
  def <<((klass, *params))
24
23
  if @service.parallel?
25
- @jobs << { 'parallel' => [], 'result_key' => next_result_key } unless @jobs.last && @jobs.last['parallel']
24
+ @jobs << new_parallel_step unless continue_existing_parallel_step?
26
25
  @jobs.last['parallel'] << [klass.name, params]
27
26
  else
28
- @jobs << { 'series' => [klass.name, params], 'result_key' => next_result_key }
27
+ @jobs << { 'series' => [klass.name, params], 'result_key_index' => next_result_key_index }
29
28
  end
30
29
  end
31
30
 
32
- def next_result_key
31
+ def next_result_key_index
33
32
  @result_key_index += 1
34
- "#{@result_key_prefix}-#{@result_key_index}"
33
+ end
34
+
35
+ private
36
+
37
+ def new_parallel_step
38
+ { 'parallel' => [], 'result_key_index' => next_result_key_index, 'parallel_key' => @service.parallel_key }
39
+ end
40
+
41
+ def continue_existing_parallel_step?
42
+ @jobs.last && @jobs.last['parallel_key'] == @service.parallel_key
35
43
  end
36
44
  end
37
45
  end
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  class Clutch
3
- VERSION = "1.1.0"
3
+ VERSION = '2.1.0'.freeze
4
4
  end
5
5
  end
@@ -15,7 +15,7 @@ module Sidekiq
15
15
  Sidekiq.redis do |redis|
16
16
  redis.multi do |multi|
17
17
  multi.rpush(current_result_key, result.to_json)
18
- multi.expire(current_result_key, RESULT_KEY_EXPIRATION_DURATION)
18
+ multi.expire(current_result_key, TEMPORARY_KEY_EXPIRATION_DURATION)
19
19
  end
20
20
  end
21
21
  end
@@ -6,22 +6,25 @@ require 'sidekiq/clutch/job_wrapper'
6
6
 
7
7
  module Sidekiq
8
8
  class Clutch
9
+ # 22 days - how long a Sidekiq job can live with exponential backoff
10
+ TEMPORARY_KEY_EXPIRATION_DURATION = 22 * 24 * 60 * 60
11
+
9
12
  def initialize(batch = nil)
10
13
  @batch = batch || Sidekiq::Batch.new
11
14
  end
12
15
 
13
- attr_reader :batch, :queue
16
+ attr_reader :batch, :queue, :parallel_key
14
17
 
15
18
  attr_accessor :current_result_key, :on_failure
16
19
 
17
20
  def parallel
18
- @parallel = true
21
+ @parallel_key = SecureRandom.uuid
19
22
  yield
20
- @parallel = false
23
+ @parallel_key = nil
21
24
  end
22
25
 
23
26
  def parallel?
24
- @parallel == true
27
+ !!@parallel_key
25
28
  end
26
29
 
27
30
  def jobs
@@ -51,10 +54,12 @@ module Sidekiq
51
54
  def setup_batch
52
55
  jobs_queue = jobs.raw.dup
53
56
  step = jobs_queue.shift
57
+ set_jobs_data_in_redis(jobs_queue)
54
58
  return if step.nil?
55
59
  batch.callback_queue = queue if queue
56
- batch.on(:success, Sidekiq::Clutch, 'jobs' => jobs_queue.dup, 'result_key' => step['result_key'])
57
- batch.on(:complete, Sidekiq::Clutch, 'on_failure' => on_failure&.name)
60
+ batch.on(:success, Sidekiq::Clutch, 'key_base' => key_base, 'result_key_index' => result_key_index(step))
61
+ on_failure_name = on_failure&.name
62
+ batch.on(:complete, Sidekiq::Clutch, 'on_failure' => on_failure_name) if on_failure_name
58
63
  batch.jobs do
59
64
  if step['series']
60
65
  series_step(step)
@@ -67,14 +72,23 @@ module Sidekiq
67
72
  end
68
73
 
69
74
  def on_success(status, options)
70
- if options['jobs'].empty?
71
- clean_up_result_keys(options['result_key'].sub(/-\d+$/, ''))
75
+ return on_success_legacy(status, options) if options['jobs']
76
+
77
+ raise 'invariant: key_base is missing!' unless options['key_base']
78
+ raise 'invariant: result_key_index is missing!' unless options['result_key_index']
79
+
80
+ # NOTE: This is a brand new instance of Sidekiq::Clutch that Sidekiq instantiates,
81
+ # so we need to set @key_base again.
82
+ @key_base = options['key_base']
83
+ remaining_jobs = JSON.parse(Sidekiq.redis { |r| r.get(jobs_key) })
84
+ if remaining_jobs.empty?
85
+ clean_up_temporary_keys
72
86
  return
73
87
  end
74
88
  parent_batch = Sidekiq::Batch.new(status.parent_bid)
75
89
  service = self.class.new(parent_batch)
76
- service.jobs.raw = options['jobs']
77
- service.current_result_key = options['result_key']
90
+ service.jobs.raw = remaining_jobs
91
+ service.current_result_key = "#{key_base}-#{options['result_key_index']}"
78
92
  service.engage
79
93
  end
80
94
 
@@ -86,19 +100,61 @@ module Sidekiq
86
100
 
87
101
  private
88
102
 
103
+ # accept old style of passing job data, will be removed in 3.0
104
+ def on_success_legacy(status, options)
105
+ @key_base = options['result_key'].sub(/-\d+$/, '')
106
+ if options['jobs'].empty?
107
+ clean_up_result_keys
108
+ return
109
+ end
110
+ parent_batch = Sidekiq::Batch.new(status.parent_bid)
111
+ service = self.class.new(parent_batch)
112
+ service.jobs.raw = options['jobs']
113
+ service.current_result_key = options['result_key']
114
+ service.engage
115
+ end
116
+
117
+ def key_base
118
+ @key_base ||= SecureRandom.uuid
119
+ end
120
+
121
+ def jobs_key
122
+ "#{key_base}-jobs"
123
+ end
124
+
125
+ def set_jobs_data_in_redis(data)
126
+ Sidekiq.redis do |redis|
127
+ redis.multi do |multi|
128
+ multi.set(jobs_key, data.to_json)
129
+ multi.expire(jobs_key, TEMPORARY_KEY_EXPIRATION_DURATION)
130
+ end
131
+ end
132
+ end
133
+
89
134
  def series_step(step)
90
135
  (klass, params) = step['series']
91
- enqueue_job(klass, params, step['result_key'])
136
+ enqueue_job(klass, params, result_key_index(step))
92
137
  end
93
138
 
94
139
  def parallel_step(step)
95
140
  step['parallel'].each do |(klass, params)|
96
- enqueue_job(klass, params, step['result_key'])
141
+ enqueue_job(klass, params, result_key_index(step))
142
+ end
143
+ end
144
+
145
+ def result_key_index(step)
146
+ if step['result_key_index']
147
+ step['result_key_index']
148
+ elsif step['result_key'] # legacy style, will be removed in 3.0
149
+ step['result_key'].split('-').last.to_i
150
+ else
151
+ raise "invariant: expected result_key_index passed in step; got: #{step.inspect}"
97
152
  end
98
153
  end
99
154
 
100
- def enqueue_job(klass, params, result_key)
155
+ def enqueue_job(klass, params, result_key_index)
101
156
  job_options = Object.const_get(klass).sidekiq_options
157
+ result_key = "#{key_base}-#{result_key_index}"
102
158
  options = {
103
159
  'class' => JobWrapper,
104
160
  'queue' => queue || job_options['queue'],
@@ -110,10 +166,14 @@ module Sidekiq
110
166
  Sidekiq::Client.push(options)
111
167
  end
112
168
 
113
- def clean_up_result_keys(key_base)
169
+ def clean_up_temporary_keys
114
170
  Sidekiq.redis do |redis|
115
- redis.keys(key_base + '*').each do |key|
116
- redis.del(key)
171
+ redis.del(jobs_key)
172
+ result_key_index = 1
173
+ loop do
174
+ result = redis.del("#{key_base}-#{result_key_index}")
175
+ result_key_index += 1
176
+ break if result == 0
117
177
  end
118
178
  end
119
179
  end
@@ -15,14 +15,13 @@ Gem::Specification.new do |spec|
15
15
  spec.license = 'MIT'
16
16
 
17
17
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
18
- `git ls-files`.split("\n").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ `git ls-files`.split("\n").reject { |f| f.match(%r{^(spec|\.ruby-version)}) }
19
19
  end
20
20
  spec.require_paths = ['lib']
21
21
 
22
22
  spec.add_dependency 'sidekiq', '>= 5.0.0'
23
23
 
24
- spec.add_development_dependency 'pry'
25
- spec.add_development_dependency 'bundler', '~> 1.16'
26
- spec.add_development_dependency 'rake', '~> 10.0'
27
- spec.add_development_dependency 'rspec', '~> 3.0'
24
+ spec.add_development_dependency 'bundler', '~> 2.2.4'
25
+ spec.add_development_dependency 'rake', '~> 13.0.1'
26
+ spec.add_development_dependency 'rspec', '~> 3.9.0'
28
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-clutch
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Morgan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-05 00:00:00.000000000 Z
11
+ date: 2021-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sidekiq
@@ -24,62 +24,48 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 5.0.0
27
- - !ruby/object:Gem::Dependency
28
- name: pry
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: bundler
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - "~>"
46
32
  - !ruby/object:Gem::Version
47
- version: '1.16'
33
+ version: 2.2.4
48
34
  type: :development
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
38
  - - "~>"
53
39
  - !ruby/object:Gem::Version
54
- version: '1.16'
40
+ version: 2.2.4
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: rake
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - "~>"
60
46
  - !ruby/object:Gem::Version
61
- version: '10.0'
47
+ version: 13.0.1
62
48
  type: :development
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
52
  - - "~>"
67
53
  - !ruby/object:Gem::Version
68
- version: '10.0'
54
+ version: 13.0.1
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: rspec
71
57
  requirement: !ruby/object:Gem::Requirement
72
58
  requirements:
73
59
  - - "~>"
74
60
  - !ruby/object:Gem::Version
75
- version: '3.0'
61
+ version: 3.9.0
76
62
  type: :development
77
63
  prerelease: false
78
64
  version_requirements: !ruby/object:Gem::Requirement
79
65
  requirements:
80
66
  - - "~>"
81
67
  - !ruby/object:Gem::Version
82
- version: '3.0'
68
+ version: 3.9.0
83
69
  description: Sidekiq::Clutch provides an ergonomic wrapper API for Sidekiq Batches
84
70
  so you can easily manage serial and parallel jobs.
85
71
  email:
@@ -122,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
108
  - !ruby/object:Gem::Version
123
109
  version: '0'
124
110
  requirements: []
125
- rubygems_version: 3.0.6
111
+ rubygems_version: 3.1.6
126
112
  signing_key:
127
113
  specification_version: 4
128
114
  summary: An ergonomic wrapper API for Sidekiq Batches