sidekiq-clutch 1.1.0 → 2.1.0

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 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