sidekiq-batch 0.1.8 → 0.2.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: 56a9cb0fe2d48eba0959e20ad98e52e62e326bb71168f13f4c48e0ad33079447
4
- data.tar.gz: ae145f7d0381f9a83ec2f0ba93be5a290589b1cd77e7544fc20b4f15fc823d0e
3
+ metadata.gz: e0e119457317bdc0bbe60785e41d4fff908f0d818ec760802b96fd9905ad14fc
4
+ data.tar.gz: 46cd957ceb643e946a6bf5ebb49ae93e14e3514384753fa7d910da0476e720b3
5
5
  SHA512:
6
- metadata.gz: 45aee8ac26397847e5738ebf21b34a7505cd367d64596b9e72ff1c51993c622e53850999cb9bcae4fa2152c7e0471c65d3c824af3af06cdefecee199b4f7c8a5
7
- data.tar.gz: 294c3bb0b9f908ee2aabf6dd62adcb296ff6409a148b3db77a456f50c728f369583bf15af4d2aa034947fb5175741985549c70d5328acb8ff471332689ad27c7
6
+ metadata.gz: c86dcadbf158fb9a533163ad042e77d18192241ab7985c4204b6526f78d3cb307a08a48fc173dcef7a7697dfb7316a847fe664ec098178b9e244d7d279978fbb
7
+ data.tar.gz: a070c7c91c502c06adc83269c3f28b97789250a80b8518ce80ba48511ddacb80537e7ffe9075c03f45062384a53b50fd03af46cb9f3b8dba39e3f03f0498e8a8
@@ -0,0 +1,3 @@
1
+ # These are supported funding model platforms
2
+ github: breamware
3
+ open_collective: sidekiq-batch
@@ -1,16 +1,30 @@
1
1
  name: CI
2
2
 
3
- on: [push, pull_request]
3
+ on: [pull_request, workflow_dispatch]
4
4
 
5
5
  jobs:
6
6
  test:
7
+ env:
8
+ REDIS_HOST: 'redis'
7
9
 
8
10
  runs-on: ubuntu-latest
11
+ services:
12
+ redis:
13
+ image: redis
14
+ # Set health checks to wait until redis has started
15
+ options: >-
16
+ --health-cmd "redis-cli ping"
17
+ --health-interval 10s
18
+ --health-timeout 5s
19
+ --health-retries 5
20
+ ports:
21
+ # Maps port 6379 on service container to the host
22
+ - 6379:6379
9
23
 
10
24
  strategy:
11
25
  fail-fast: false
12
26
  matrix:
13
- ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", ruby-head]
27
+ ruby: ["3.0", "3.1", "3.2", ruby-head]
14
28
 
15
29
  steps:
16
30
  - uses: actions/checkout@v2
@@ -0,0 +1,70 @@
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL"
13
+
14
+ on:
15
+ push:
16
+ branches: [ master ]
17
+ pull_request:
18
+ # The branches below must be a subset of the branches above
19
+ branches: [ master ]
20
+ schedule:
21
+ - cron: '24 3 * * 2'
22
+
23
+ jobs:
24
+ analyze:
25
+ name: Analyze
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ actions: read
29
+ contents: read
30
+ security-events: write
31
+
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ language: [ 'ruby' ]
36
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37
+ # Learn more about CodeQL language support at https://git.io/codeql-language-support
38
+
39
+ steps:
40
+ - name: Checkout repository
41
+ uses: actions/checkout@v3
42
+
43
+ # Initializes the CodeQL tools for scanning.
44
+ - name: Initialize CodeQL
45
+ uses: github/codeql-action/init@v2
46
+ with:
47
+ languages: ${{ matrix.language }}
48
+ # If you wish to specify custom queries, you can do so here or in a config file.
49
+ # By default, queries listed here will override any specified in a config file.
50
+ # Prefix the list here with "+" to use these queries and those in the config file.
51
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
52
+
53
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54
+ # If this step fails, then you should remove it and run the build manually (see below)
55
+ - name: Autobuild
56
+ uses: github/codeql-action/autobuild@v2
57
+
58
+ # ℹ️ Command-line programs to run using the OS shell.
59
+ # 📚 https://git.io/JvXDl
60
+
61
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62
+ # and modify them (or add more) to build your code if your project
63
+ # uses a compiled language
64
+
65
+ #- run: |
66
+ # make bootstrap
67
+ # make release
68
+
69
+ - name: Perform CodeQL Analysis
70
+ uses: github/codeql-action/analyze@v2
data/README.md CHANGED
@@ -32,7 +32,41 @@ Or install it yourself as:
32
32
 
33
33
  ## Usage
34
34
 
35
- Sidekiq Batch is drop-in replacement for the API from Sidekiq PRO. See https://github.com/mperham/sidekiq/wiki/Batches for usage.
35
+ Sidekiq Batch is MOSTLY a drop-in replacement for the API from Sidekiq PRO. See https://github.com/mperham/sidekiq/wiki/Batches for usage.
36
+
37
+ ## Caveats/Gotchas
38
+
39
+ Consider the following workflow:
40
+
41
+ * Batch Z created
42
+ * Worker A queued in batch Z
43
+ * Worker A starts Worker B in batch Z
44
+ * Worker B completes *before* worker A does
45
+ * Worker A completes
46
+
47
+ In the standard configuration, the `on(:success)` and `on(:complete)` callbacks will be triggered when Worker B completes.
48
+ This configuration is the default, simply for legacy reasons. This gem adds the following option to the sidekiq.yml options:
49
+
50
+ ```yaml
51
+ :batch_push_interval: 0
52
+ ```
53
+
54
+ When this value is *absent* (aka legacy), Worker A will only push the increment of batch jobs (aka Worker B) *when it completes*
55
+
56
+ When this value is set to `0`, Worker A will increment the count as soon as `WorkerB.perform_async` is called
57
+
58
+ When this value is a positive number, Worker A will wait a maximum of value-seconds before pushing the increment to redis, or until it's done, whichever comes first.
59
+
60
+ This comes into play if Worker A is queueing thousands of WorkerB jobs, or has some other reason for WorkerB to complete beforehand.
61
+
62
+ If you are queueing many WorkerB jobs, it is recommended to set this value to something like `3` to avoid thousands of calls to redis, and call WorkerB like so:
63
+ ```ruby
64
+ WorkerB.perform_in(4.seconds, some, args)
65
+ ```
66
+ this will ensure that the batch callback does not get triggered until WorkerA *and* the last WorkerB job complete.
67
+
68
+ If WorkerA is just slow for whatever reason, setting to `0` will update the batch status immediately so that the callbacks don't fire.
69
+
36
70
 
37
71
  ## Contributing
38
72
 
@@ -40,10 +40,10 @@ module Sidekiq
40
40
 
41
41
  _, _, success, _, complete, pending, children, failure = Sidekiq.redis do |r|
42
42
  r.multi do |pipeline|
43
- pipeline.sadd("BID-#{parent_bid}-success", bid)
43
+ pipeline.sadd("BID-#{parent_bid}-success", [bid])
44
44
  pipeline.expire("BID-#{parent_bid}-success", Sidekiq::Batch::BID_EXPIRE_TTL)
45
45
  pipeline.scard("BID-#{parent_bid}-success")
46
- pipeline.sadd("BID-#{parent_bid}-complete", bid)
46
+ pipeline.sadd("BID-#{parent_bid}-complete", [bid])
47
47
  pipeline.scard("BID-#{parent_bid}-complete")
48
48
  pipeline.hincrby("BID-#{parent_bid}", "pending", 0)
49
49
  pipeline.hincrby("BID-#{parent_bid}", "children", 0)
@@ -81,7 +81,7 @@ module Sidekiq
81
81
  Sidekiq.logger.debug {"Finalize parent complete bid: #{parent_bid}"}
82
82
  _, complete, pending, children, failure = Sidekiq.redis do |r|
83
83
  r.multi do |pipeline|
84
- pipeline.sadd("BID-#{parent_bid}-complete", bid)
84
+ pipeline.sadd("BID-#{parent_bid}-complete", [bid])
85
85
  pipeline.scard("BID-#{parent_bid}-complete")
86
86
  pipeline.hincrby("BID-#{parent_bid}", "pending", 0)
87
87
  pipeline.hincrby("BID-#{parent_bid}", "children", 0)
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  class Batch
3
- VERSION = '0.1.8'.freeze
3
+ VERSION = '0.2.0'.freeze
4
4
  end
5
5
  end
data/lib/sidekiq/batch.rb CHANGED
@@ -20,7 +20,11 @@ module Sidekiq
20
20
  @initialized = false
21
21
  @created_at = Time.now.utc.to_f
22
22
  @bidkey = "BID-" + @bid.to_s
23
- @ready_to_queue = []
23
+ @queued_jids = []
24
+ @pending_jids = []
25
+
26
+ @incremental_push = !Sidekiq.default_configuration[:batch_push_interval].nil?
27
+ @batch_push_interval = Sidekiq.default_configuration[:batch_push_interval]
24
28
  end
25
29
 
26
30
  def description=(description)
@@ -43,10 +47,10 @@ module Sidekiq
43
47
  callback_key = "#{@bidkey}-callbacks-#{event}"
44
48
  Sidekiq.redis do |r|
45
49
  r.multi do |pipeline|
46
- pipeline.sadd(callback_key, JSON.unparse({
50
+ pipeline.sadd(callback_key, [JSON.unparse({
47
51
  callback: callback,
48
52
  opts: options
49
- }))
53
+ })])
50
54
  pipeline.expire(callback_key, BID_EXPIRE_TTL)
51
55
  end
52
56
  end
@@ -64,51 +68,86 @@ module Sidekiq
64
68
  Sidekiq.redis do |r|
65
69
  r.multi do |pipeline|
66
70
  pipeline.hset(@bidkey, "created_at", @created_at)
67
- pipeline.hset(@bidkey, "parent_bid", parent_bid.to_s) if parent_bid
68
71
  pipeline.expire(@bidkey, BID_EXPIRE_TTL)
72
+ if parent_bid
73
+ pipeline.hset(@bidkey, "parent_bid", parent_bid.to_s)
74
+ pipeline.hincrby("BID-#{parent_bid}", "children", 1)
75
+ end
69
76
  end
70
77
  end
71
78
 
72
79
  @initialized = true
73
80
  end
74
81
 
75
- @ready_to_queue = []
82
+ @queued_jids = []
83
+ @pending_jids = []
76
84
 
77
85
  begin
78
86
  parent = Thread.current[:batch]
79
87
  Thread.current[:batch] = self
88
+ Thread.current[:parent_bid] = parent_bid
80
89
  yield
81
90
  ensure
82
91
  Thread.current[:batch] = parent
92
+ Thread.current[:parent_bid] = nil
83
93
  end
84
94
 
85
- return [] if @ready_to_queue.size == 0
95
+ return [] if @queued_jids.size == 0
96
+ conditional_redis_increment!(true)
86
97
 
87
98
  Sidekiq.redis do |r|
88
99
  r.multi do |pipeline|
89
100
  if parent_bid
90
- pipeline.hincrby("BID-#{parent_bid}", "children", 1)
91
- pipeline.hincrby("BID-#{parent_bid}", "total", @ready_to_queue.size)
92
101
  pipeline.expire("BID-#{parent_bid}", BID_EXPIRE_TTL)
93
102
  end
94
103
 
95
- pipeline.hincrby(@bidkey, "pending", @ready_to_queue.size)
96
- pipeline.hincrby(@bidkey, "total", @ready_to_queue.size)
97
104
  pipeline.expire(@bidkey, BID_EXPIRE_TTL)
98
105
 
99
- pipeline.sadd(@bidkey + "-jids", @ready_to_queue)
106
+ pipeline.sadd(@bidkey + "-jids", @queued_jids)
100
107
  pipeline.expire(@bidkey + "-jids", BID_EXPIRE_TTL)
101
108
  end
102
109
  end
103
110
 
104
- @ready_to_queue
111
+ @queued_jids
105
112
  ensure
106
113
  Thread.current[:bid_data] = bid_data
107
114
  end
108
115
  end
109
116
 
110
117
  def increment_job_queue(jid)
111
- @ready_to_queue << jid
118
+ @queued_jids << jid
119
+ @pending_jids << jid
120
+ conditional_redis_increment!
121
+ end
122
+
123
+ def conditional_redis_increment!(force=false)
124
+ if should_increment? || force
125
+ parent_bid = Thread.current[:parent_bid]
126
+ Sidekiq.redis do |r|
127
+ r.multi do |pipeline|
128
+ if parent_bid
129
+ pipeline.hincrby("BID-#{parent_bid}", "total", @pending_jids.length)
130
+ pipeline.expire("BID-#{parent_bid}", BID_EXPIRE_TTL)
131
+ end
132
+
133
+ pipeline.hincrby(@bidkey, "pending", @pending_jids.length)
134
+ pipeline.hincrby(@bidkey, "total", @pending_jids.length)
135
+ pipeline.expire(@bidkey, BID_EXPIRE_TTL)
136
+ end
137
+ end
138
+ @pending_jids = []
139
+ end
140
+ end
141
+
142
+ def should_increment?
143
+ return false unless @incremental_push
144
+ return true if @batch_push_interval == 0 || @queued_jids.length == 1
145
+ now = Time.now.to_f
146
+ @last_increment ||= now
147
+ if @last_increment + @batch_push_interval > now
148
+ @last_increment = now
149
+ return true
150
+ end
112
151
  end
113
152
 
114
153
  def invalidate_all
@@ -130,7 +169,7 @@ module Sidekiq
130
169
  end
131
170
 
132
171
  def valid?(batch = self)
133
- valid = !Sidekiq.redis { |r| r.exists("invalidated-bid-#{batch.bid}") }
172
+ valid = Sidekiq.redis { |r| r.exists("invalidated-bid-#{batch.bid}") }.zero?
134
173
  batch.parent ? valid && valid?(batch.parent) : valid
135
174
  end
136
175
 
@@ -149,7 +188,7 @@ module Sidekiq
149
188
  def process_failed_job(bid, jid)
150
189
  _, pending, failed, children, complete, parent_bid = Sidekiq.redis do |r|
151
190
  r.multi do |pipeline|
152
- pipeline.sadd("BID-#{bid}-failed", jid)
191
+ pipeline.sadd("BID-#{bid}-failed", [jid])
153
192
 
154
193
  pipeline.hincrby("BID-#{bid}", "pending", 0)
155
194
  pipeline.scard("BID-#{bid}-failed")
@@ -166,7 +205,7 @@ module Sidekiq
166
205
  Sidekiq.redis do |r|
167
206
  r.multi do |pipeline|
168
207
  pipeline.hincrby("BID-#{parent_bid}", "pending", 1)
169
- pipeline.sadd("BID-#{parent_bid}-failed", jid)
208
+ pipeline.sadd("BID-#{parent_bid}-failed", [jid])
170
209
  pipeline.expire("BID-#{parent_bid}-failed", BID_EXPIRE_TTL)
171
210
  end
172
211
  end
@@ -188,8 +227,8 @@ module Sidekiq
188
227
  pipeline.hget("BID-#{bid}", "total")
189
228
  pipeline.hget("BID-#{bid}", "parent_bid")
190
229
 
191
- pipeline.srem("BID-#{bid}-failed", jid)
192
- pipeline.srem("BID-#{bid}-jids", jid)
230
+ pipeline.srem("BID-#{bid}-failed", [jid])
231
+ pipeline.srem("BID-#{bid}-jids", [jid])
193
232
  pipeline.expire("BID-#{bid}", BID_EXPIRE_TTL)
194
233
  end
195
234
  end
@@ -209,7 +248,7 @@ module Sidekiq
209
248
  already_processed, _, callbacks, queue, parent_bid, callback_batch = Sidekiq.redis do |r|
210
249
  r.multi do |pipeline|
211
250
  pipeline.hget(batch_key, event_name)
212
- pipeline.hset(batch_key, event_name, true)
251
+ pipeline.hset(batch_key, event_name, 'true')
213
252
  pipeline.smembers(callback_key)
214
253
  pipeline.hget(batch_key, "callback_queue")
215
254
  pipeline.hget(batch_key, "parent_bid")
@@ -253,7 +292,7 @@ module Sidekiq
253
292
  else
254
293
  # Otherwise finalize in sub batch complete callback
255
294
  cb_batch = self.new
256
- cb_batch.callback_batch = true
295
+ cb_batch.callback_batch = 'true'
257
296
  Sidekiq.logger.debug {"Adding callback batch: #{cb_batch.bid} for batch: #{bid}"}
258
297
  cb_batch.on(:complete, "Sidekiq::Batch::Callback::Finalize#dispatch", opts)
259
298
  cb_batch.jobs do
@@ -19,10 +19,9 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "sidekiq", ">= 3"
22
+ spec.add_dependency "sidekiq", ">= 7", "<8"
23
23
 
24
24
  spec.add_development_dependency "bundler", "~> 2.1"
25
25
  spec.add_development_dependency "rake", "~> 13.0"
26
26
  spec.add_development_dependency "rspec", "~> 3.0"
27
- spec.add_development_dependency "fakeredis", "~> 0.8.0"
28
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-batch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcin Naglik
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-24 00:00:00.000000000 Z
11
+ date: 2024-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sidekiq
@@ -16,14 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '3'
19
+ version: '7'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '8'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: '3'
29
+ version: '7'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '8'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: bundler
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -66,20 +72,6 @@ dependencies:
66
72
  - - "~>"
67
73
  - !ruby/object:Gem::Version
68
74
  version: '3.0'
69
- - !ruby/object:Gem::Dependency
70
- name: fakeredis
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 0.8.0
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 0.8.0
83
75
  description: Sidekiq Batch Jobs Implementation
84
76
  email:
85
77
  - marcin.naglik@gmail.com
@@ -87,8 +79,10 @@ executables: []
87
79
  extensions: []
88
80
  extra_rdoc_files: []
89
81
  files:
82
+ - ".github/FUNDING.yml"
90
83
  - ".github/dependabot.yml"
91
84
  - ".github/workflows/ci.yml"
85
+ - ".github/workflows/codeql-analysis.yml"
92
86
  - ".github/workflows/stale.yml"
93
87
  - ".gitignore"
94
88
  - ".rspec"
@@ -124,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
118
  - !ruby/object:Gem::Version
125
119
  version: '0'
126
120
  requirements: []
127
- rubygems_version: 3.3.7
121
+ rubygems_version: 3.5.11
128
122
  signing_key:
129
123
  specification_version: 4
130
124
  summary: Sidekiq Batch Jobs