sidekiq-robust-job 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +9 -0
  5. data/Changelog.md +4 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +127 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +291 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/lib/sidekiq-robust-job.rb +1 -0
  14. data/lib/sidekiq/robust/job.rb +1 -0
  15. data/lib/sidekiq/robust/job/version.rb +7 -0
  16. data/lib/sidekiq_robust_job.rb +64 -0
  17. data/lib/sidekiq_robust_job/configuration.rb +48 -0
  18. data/lib/sidekiq_robust_job/dependencies_container.rb +64 -0
  19. data/lib/sidekiq_robust_job/digest_generator.rb +14 -0
  20. data/lib/sidekiq_robust_job/enqueue_conflict_resolution_strategy.rb +58 -0
  21. data/lib/sidekiq_robust_job/enqueue_conflict_resolution_strategy/base.rb +17 -0
  22. data/lib/sidekiq_robust_job/enqueue_conflict_resolution_strategy/do_nothing.rb +8 -0
  23. data/lib/sidekiq_robust_job/enqueue_conflict_resolution_strategy/drop_self.rb +12 -0
  24. data/lib/sidekiq_robust_job/enqueue_conflict_resolution_strategy/replace.rb +13 -0
  25. data/lib/sidekiq_robust_job/missed_jobs.rb +26 -0
  26. data/lib/sidekiq_robust_job/missed_jobs_scheduler.rb +56 -0
  27. data/lib/sidekiq_robust_job/model.rb +90 -0
  28. data/lib/sidekiq_robust_job/perform_missed_jobs_job.rb +9 -0
  29. data/lib/sidekiq_robust_job/repository.rb +50 -0
  30. data/lib/sidekiq_robust_job/setter_proxy_job.rb +70 -0
  31. data/lib/sidekiq_robust_job/sidekiq_job_extensions.rb +34 -0
  32. data/lib/sidekiq_robust_job/sidekiq_job_manager.rb +80 -0
  33. data/lib/sidekiq_robust_job/support/matchers/enqueue_sidekiq_robust_job.rb +71 -0
  34. data/lib/sidekiq_robust_job/uniqueness_strategy.rb +66 -0
  35. data/lib/sidekiq_robust_job/uniqueness_strategy/base.rb +46 -0
  36. data/lib/sidekiq_robust_job/uniqueness_strategy/no_uniqueness.rb +9 -0
  37. data/lib/sidekiq_robust_job/uniqueness_strategy/until_executed.rb +16 -0
  38. data/lib/sidekiq_robust_job/uniqueness_strategy/until_executing.rb +16 -0
  39. data/sidekiq-robust-job.gemspec +42 -0
  40. metadata +238 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2c4c75298ef6032a3493a8b34871ba84575d1e59fa0d8a21088b3290542b74b3
4
+ data.tar.gz: 05d1a83d06300c4f273553f07a6ecc38a78f9eddea8539e1d7b85345d53de2f9
5
+ SHA512:
6
+ metadata.gz: f9e79a683e532d8078975b9e876db178218a5c5e2937cd734f61c37107f5a1d53b62ee95f8f4d7cf96c7869a970fcf919965420ec75df84029f2d8847574d3ff
7
+ data.tar.gz: 31067138cd1c737ccc690908b5e75d12fa7d8d5e7f55e086834566ff2e115d4b2664c6b56126880d44468fe6c7953dd3eebd99f49464c8490087b5180104add2
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /spec/test.db
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,9 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.1
6
+ services:
7
+ - postgresql
8
+ - redis-server
9
+ before_install: gem install bundler -v 2.1.4
@@ -0,0 +1,4 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sidekiq-robust-job.gemspec
4
+ gemspec
@@ -0,0 +1,127 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sidekiq-robust-job (0.1.0)
5
+ activesupport (>= 5)
6
+ sidekiq (>= 5)
7
+ sidekiq-cron (~> 1)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actionpack (5.2.4.4)
13
+ actionview (= 5.2.4.4)
14
+ activesupport (= 5.2.4.4)
15
+ rack (~> 2.0, >= 2.0.8)
16
+ rack-test (>= 0.6.3)
17
+ rails-dom-testing (~> 2.0)
18
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
19
+ actionview (5.2.4.4)
20
+ activesupport (= 5.2.4.4)
21
+ builder (~> 3.1)
22
+ erubi (~> 1.4)
23
+ rails-dom-testing (~> 2.0)
24
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
25
+ activemodel (5.2.4.4)
26
+ activesupport (= 5.2.4.4)
27
+ activerecord (5.2.4.4)
28
+ activemodel (= 5.2.4.4)
29
+ activesupport (= 5.2.4.4)
30
+ arel (>= 9.0)
31
+ activesupport (5.2.4.4)
32
+ concurrent-ruby (~> 1.0, >= 1.0.2)
33
+ i18n (>= 0.7, < 2)
34
+ minitest (~> 5.1)
35
+ tzinfo (~> 1.1)
36
+ arel (9.0.0)
37
+ builder (3.2.4)
38
+ concurrent-ruby (1.1.7)
39
+ connection_pool (2.2.3)
40
+ crass (1.0.6)
41
+ diff-lcs (1.4.4)
42
+ erubi (1.10.0)
43
+ et-orbi (1.2.4)
44
+ tzinfo
45
+ factory_bot (6.1.0)
46
+ activesupport (>= 5.0.0)
47
+ factory_bot_rails (6.1.0)
48
+ factory_bot (~> 6.1.0)
49
+ railties (>= 5.0.0)
50
+ fugit (1.4.0)
51
+ et-orbi (~> 1.1, >= 1.1.8)
52
+ raabro (~> 1.4)
53
+ i18n (1.8.5)
54
+ concurrent-ruby (~> 1.0)
55
+ loofah (2.7.0)
56
+ crass (~> 1.0.2)
57
+ nokogiri (>= 1.5.9)
58
+ method_source (1.0.0)
59
+ mini_portile2 (2.4.0)
60
+ minitest (5.14.2)
61
+ nokogiri (1.10.10)
62
+ mini_portile2 (~> 2.4.0)
63
+ pg (1.2.3)
64
+ raabro (1.4.0)
65
+ rack (2.2.3)
66
+ rack-test (1.1.0)
67
+ rack (>= 1.0, < 3)
68
+ rails-dom-testing (2.0.3)
69
+ activesupport (>= 4.2.0)
70
+ nokogiri (>= 1.6)
71
+ rails-html-sanitizer (1.3.0)
72
+ loofah (~> 2.3)
73
+ railties (5.2.4.4)
74
+ actionpack (= 5.2.4.4)
75
+ activesupport (= 5.2.4.4)
76
+ method_source
77
+ rake (>= 0.8.7)
78
+ thor (>= 0.19.0, < 2.0)
79
+ rake (13.0.1)
80
+ redis (4.2.3)
81
+ rspec (3.10.0)
82
+ rspec-core (~> 3.10.0)
83
+ rspec-expectations (~> 3.10.0)
84
+ rspec-mocks (~> 3.10.0)
85
+ rspec-core (3.10.0)
86
+ rspec-support (~> 3.10.0)
87
+ rspec-expectations (3.10.0)
88
+ diff-lcs (>= 1.2.0, < 2.0)
89
+ rspec-support (~> 3.10.0)
90
+ rspec-mocks (3.10.0)
91
+ diff-lcs (>= 1.2.0, < 2.0)
92
+ rspec-support (~> 3.10.0)
93
+ rspec-sidekiq (3.1.0)
94
+ rspec-core (~> 3.0, >= 3.0.0)
95
+ sidekiq (>= 2.4.0)
96
+ rspec-support (3.10.0)
97
+ shoulda-matchers (4.4.1)
98
+ activesupport (>= 4.2.0)
99
+ sidekiq (6.1.2)
100
+ connection_pool (>= 2.2.2)
101
+ rack (~> 2.0)
102
+ redis (>= 4.2.0)
103
+ sidekiq-cron (1.2.0)
104
+ fugit (~> 1.1)
105
+ sidekiq (>= 4.2.1)
106
+ thor (1.0.1)
107
+ thread_safe (0.3.6)
108
+ timecop (0.9.2)
109
+ tzinfo (1.2.8)
110
+ thread_safe (~> 0.1)
111
+
112
+ PLATFORMS
113
+ ruby
114
+
115
+ DEPENDENCIES
116
+ activerecord (~> 5)
117
+ factory_bot_rails
118
+ pg
119
+ rake (~> 13.0)
120
+ rspec (~> 3.0)
121
+ rspec-sidekiq
122
+ shoulda-matchers
123
+ sidekiq-robust-job!
124
+ timecop
125
+
126
+ BUNDLED WITH
127
+ 2.1.4
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Karol Galanciak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,291 @@
1
+ # SidekiqRobustJob
2
+
3
+ Make your Sidekiq jobs robust, durable and profilable - and fully take advantage of it!
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'sidekiq-robust-job'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install sidekiq-robust-job
20
+
21
+ ## Usage
22
+
23
+ The primary idea behind the gem is storing jobs in Postgres, yet, still using the entire Sidekiq's ecosystem. You may call it a DelayedJob inside Sidekiq :).
24
+
25
+ That means that enqueuing every job will mean creating another record in the database that will represent a given SidekiqJob. And it is going to be the argument of the actual job in Redis.
26
+
27
+ To get started, you need a couple of things:
28
+
29
+ 1. A proper model representing SidekiqJob:
30
+
31
+ You can use the following migration to create it:
32
+
33
+ ``` rb
34
+ class CreateSidekiqJobs < ActiveRecord::Migration[6.0]
35
+ def change
36
+ create_table :sidekiq_jobs do |t|
37
+ t.string "job_class", null: false
38
+ t.datetime "enqueued_at", null: false
39
+ t.jsonb "arguments", default: [], null: false
40
+ t.text "digest", null: false
41
+ t.string "uniqueness_strategy", null: false
42
+ t.datetime "completed_at"
43
+ t.datetime "dropped_at"
44
+ t.datetime "failed_at"
45
+ t.datetime "started_at"
46
+ t.decimal "memory_usage_before_processing_in_megabytes"
47
+ t.decimal "memory_usage_after_processing_in_megabytes"
48
+ t.decimal "memory_usage_change_in_megabytes"
49
+ t.integer "attempts", default: 0, null: false
50
+ t.string "error_type"
51
+ t.text "error_message"
52
+ t.string "queue"
53
+ t.datetime "created_at", precision: 6, null: false
54
+ t.datetime "updated_at", precision: 6, null: false
55
+ t.bigint "dropped_by_job_id"
56
+ t.string "enqueue_conflict_resolution_strategy"
57
+ t.datetime "execute_at"
58
+ t.string "sidekiq_jid"
59
+
60
+ t.index ["completed_at"], name: "index_sidekiq_jobs_on_completed_at", using: :brin
61
+ t.index ["created_at"], name: "index_sidekiq_jobs_on_created_at", using: :brin
62
+ t.index ["digest"], name: "index_sidekiq_jobs_on_digest"
63
+ t.index ["dropped_at"], name: "index_sidekiq_jobs_on_dropped_at", using: :brin
64
+ t.index ["dropped_by_job_id"], name: "index_sidekiq_jobs_on_dropped_by_job_id"
65
+ t.index ["enqueued_at"], name: "index_sidekiq_jobs_on_enqueued_at", using: :brin
66
+ t.index ["failed_at"], name: "index_sidekiq_jobs_on_failed_at", using: :brin
67
+ t.index ["job_class"], name: "index_sidekiq_jobs_on_job_class"
68
+ end
69
+ end
70
+ end
71
+ ```
72
+
73
+
74
+ and include `SidekiqRobustJob::Model` module in the job class:
75
+
76
+ ``` rb
77
+ class SidekiqJob < ApplicationRecord
78
+ include SidekiqRobustJob::Model
79
+ end
80
+ ```
81
+
82
+
83
+ 2. Adjust your job classes
84
+
85
+ You will need to include `SidekiqRobustJob::SidekiqJobExtensions` module and rename the `perform` method to `call`. When you include this module, the gem is going to override Sidekiq methods you normally use such as `perform/perform_async/perform_in/perform_at/set`, but we still need to have a method for the exection of the job, that's why we need a custom `call`. You can still use enqueuing methods (`perform_async/perform_in/perform_at/set`) exactly the same way, but the signature of `perform` will be different as it will be taking SidekiqJob's ID as an argument.
86
+
87
+
88
+ ``` rb
89
+ class MyJob
90
+ include Sidekiq::Worker
91
+ include SidekiqRobustJob::SidekiqJobExtensions
92
+
93
+ def call(user_id)
94
+ User.find(user_id).do_something
95
+ end
96
+ end
97
+ ```
98
+
99
+
100
+ 3. Add the proper initializer
101
+
102
+ ``` rb
103
+ Rails.application.config.to_prepare do
104
+ SidekiqRobustJob.configure do |config|
105
+ config.memory_monitor = GetProcessMem.new
106
+ config.clock = Time.zone
107
+ config.sidekiq_job_model = SidekiqJob
108
+ end
109
+ end
110
+ ```
111
+
112
+ This is a minimum required initializer although there are more options available. SidekiqRobustJob tracks memory usage for all jobs and `memory_monitor` expects an interface like the one from [GetProcessMem gem](https://github.com/schneems/get_process_mem). If you don't want to use that feature, you can provide some "fake" memory monitor, like this one: `OpenStruct.new(mb: 0)` (we only care about `mb` method).
113
+
114
+ ### Other features than durability
115
+
116
+ #### Enqueue Conflict Resolution Strategy
117
+
118
+ This feature is about handling a "conflict" (determined by a digest generated based on the job class and its arguments) when there is already the "same job" enqueued (i.e. same job class and arguments).
119
+
120
+ Let's say that there is already a job scheduled to be executed in 1 minute and we want to enqueue another one, exactly the same, in 5 minutes. There are 3 possible scenarios here:
121
+
122
+ 1. `do_nothing` - this is a default when you don't specify anything. In this case, both jobs will be executed.
123
+ 2. `drop_self` - **recommended**. The second job will be "dropped" - it will be created, marked as dropped (by assigning `dropped_at` timestamp and `dropped_by_job_id` that will be equal to own ID) and won't be enqueued to Redis. And of course, the first job will be executed just fine.
124
+ 3. `replace` - The first job will be "dropped" - marked as dropped (by assigning `dropped_at` timestamp and `dropped_by_job_id` that will be equal to the new Job ID). Both jobs will be enqueued in Sidekiq itself, but the real logic behind them will be executed only for the second one (that replaced the first one). The original job will be handled by Sidekiq, but it's going to return early immediately due to the status check whether it's dropped.
125
+
126
+ If you want to use this feature, declare it with other Sidekiq options in the job:
127
+
128
+ ``` rb
129
+ class MyJob
130
+ include Sidekiq::Worker
131
+ include SidekiqRobustJob::SidekiqJobExtensions
132
+
133
+ sidekiq_options queue: "critical", enqueue_conflict_resolution_strategy: "drop_self"
134
+
135
+ def call(user_id)
136
+ User.find(user_id).do_something
137
+ end
138
+ end
139
+ ```
140
+
141
+ Keep in mind that this feature will work only when jobs are still scheduled to be executed, not when they are getting already performed. If you care about ensuring uniqueness of the execution (a mutex between jobs), take a look at Execution Uniqueness feature.
142
+
143
+ Although keep in mind that using this feature comes with some performance penalty due to the extra overhead and queries.
144
+
145
+ If you have a lot of conflicts within a short period, consider using `perform_in` instead of `perform_async` and add some random number of seconds (ideally, below 1 minute) to make it easier to apply enqueue conflict resolution strategy.
146
+
147
+ #### Execution Uniqueness (Mutex)
148
+
149
+ This feature is about handling a "conflict" (determined by a digest generated based on the job class and its arguments) when there is already the "same job" getting executed (i.e. same job class and arguments) at the same time.
150
+
151
+ Let's say that there is already a job scheduled to be executed just in a moment and you are enqueuing another one to be executed right now. There are 3 possible scenarios here:
152
+
153
+ 1. `no_uniqueness` - this is a default when you don't specify anything. In this case, both jobs will be executed.
154
+ 2. `until_executed` - One of the jobs acquires mutex using Redlock. When job is finished, it drops other pending jobs (and assigns `dropped_by_job_id` equal to the job that acquired the lock) with the same digest (based on job's class and arguments), and releases the lock. The job that failed to acquire a mutex is rescheduled (not dropped though, just to be on the safe side) and will be executed in the interval determined by `reschedule_interval_in_seconds` (5 seconds by default).
155
+ 3. `until_executing` - One of the jobs acquires mutex using Redlock, it drops and assigns `dropped_by_job_id` equal to the job that acquired the lock) other pending jobs with the same digest (based on job's class and arguments) and releases the lock. And then it executes the actual logic behind the job. The job that failed to acquire a lock is rescheduled (not dropped though, just to be on the safe side) and will be executed in the interval determined by `reschedule_interval_in_seconds` (5 seconds by default).
156
+
157
+ If you want to use this feature, declare in with other Sidekiq options in the job:
158
+
159
+ ``` rb
160
+ class MyJob
161
+ include Sidekiq::Worker
162
+ include SidekiqRobustJob::SidekiqJobExtensions
163
+
164
+ sidekiq_options queue: "critical", uniqueness_strategy: "until_executed", reschedule_interval_in_seconds: 10
165
+
166
+ def call(user_id)
167
+ User.find(user_id).do_something
168
+ end
169
+ end
170
+ ```
171
+
172
+ You can use this feature together with Enqueue Conflict Resolution Strategy. Although keep in mind that using it comes with some performance penalty due to the extra overhead and queries.
173
+
174
+ Also, you need to provide the redlock handler. You can use [redlock-rb](https://github.com/leandromoreira/redlock-rb) for that and inject it in the initializer:
175
+
176
+
177
+ ``` rb
178
+ Rails.application.config.to_prepare do
179
+ SidekiqRobustJob.configure do |config|
180
+ config.memory_monitor = GetProcessMem.new
181
+ config.clock = Time.zone
182
+ config.sidekiq_job_model = SidekiqJob
183
+ config.locker = Redlock::Client.new([ENV.fetch("REDIS_URL")])
184
+ end
185
+ end
186
+ ```
187
+
188
+ You can also configure `lock_ttl_proc` setting which is used for determining TTL for the lock. By default it's 120 seconds, and for very long jobs you might want to reconsider it. You can use a custom lambda (or a service responding to `call` method) to resolve this value based on the job's attributes as the lambda is expected to take a single argument - the job itself:
189
+
190
+ ``` rb
191
+ config.lock_ttl_proc = ->(job) { somehow_determine_it_based_on_the_job(job) }
192
+ ```
193
+
194
+ #### Missed Jobs Periodical Handler
195
+
196
+ Recommended especially when you don't use Sidekiq Pro's `super_fetch`. If you dequeue job from Redis and the process is killed (by OOM, for example) then good luck with having the job finished. However, if the job is stored in Postgres, this is not an issue. You can just look for the jobs that look as if they were missed and re-enqueue them. Periodically.
197
+
198
+ If you want to take advantage of this feature, just add the job to schedule (based on [sidekiq-cron](https://github.com/ondrejbartas/sidekiq-cron)):
199
+
200
+ ``` rb
201
+ SidekiqRobustJob.schedule_missed_jobs_handling
202
+ ```
203
+
204
+ By default, the job will be executed every 3 hours. It is going to look for the jobs created more than 3 hours ago that are still not completed or not dropped and reschedule them. You can customize both how often the job is executed and when the job should be considered to be missed:
205
+
206
+ ```
207
+ config.missed_job_cron = "0 */3 * * *"
208
+ config.missed_job_policy = ->(job) { Time.current > (job.created_at + 3.hours) }
209
+ ```
210
+
211
+ #### Getting More Insight About Jobs
212
+
213
+ There are a lot of things that are stored in the Postgres for each job that might give you a lot of insight about multiple things and use them for some sort of profiling:
214
+
215
+ - `job_class` - a class representing a given job
216
+ - `enqueued_at` - when the job was pushed to Sidekiq
217
+ - `arguments` - arguments for the job execution (that will be passed to `call` method)
218
+ - `digest` - a digest determined by job's class and its arguments
219
+ - `uniqueness_strategy` - an execution uniqueness strategy(mutex) to be used when executing the job
220
+ - `completed_at` - when the job was completed
221
+ - `dropped_at` - when the job was dropped
222
+ - `failed_at` - when the job failed (when the exception was raised)
223
+ - `started_at` - when the job was dequeued and started getting executed
224
+ - `memory_usage_before_processing_in_megabytes` - memory usage of the worker before the execution of the job
225
+ - `memory_usage_after_processing_in_megabytes` - memory usage of the worker after the execution of the job
226
+ - `memory_usage_change_in_megabytes` - the difference between after and before. Useful when looking for some outliers that require more memory than the others.
227
+ - `attempts` - how many times there was an attempt to execute this job. For successful ones, this is most likely going to be 1 unless there were some exceptions rased.
228
+ - `error_type` - a class of the exception if it was raised
229
+ - `error_message` - an error message coming from the exception if it was raised
230
+ - `queue` - name of the Sidekiq queue where the job was pushed to
231
+ - `dropped_by_job_id` - ID of the job that dropped this particular job
232
+ - `enqueue_conflict_resolution_strategy` - the name of the strategy for handling conflict when enqueuing the job
233
+ - `execute_at` - when the job is supposed to be executed (mostly when using `perform_in`/`perform_at`)
234
+ - `sidekiq_jid` - Sidekiq's Job ID (the one stored in Redis)
235
+
236
+ #### Neat Matcher For Testing
237
+
238
+ A nice bonus on top to make it easy to test: `enqueue_sidekiq_robust_job` matcher that can be chained with `with` (for job's arguments) and `in` or `at` (when using `perform_in` or `perform_at`).
239
+
240
+ First, make the matcher available:
241
+
242
+ ``` rb
243
+ require "sidekiq_robust_job/support/matchers/enqueue_sidekiq_robust_job"
244
+ ```
245
+
246
+ And use it in your specs.
247
+
248
+ When using `perform_async`:
249
+
250
+ ``` rb
251
+ expect {
252
+ call
253
+ }.to enqueue_sidekiq_robust_job(MyJob).with(user.id)
254
+ ```
255
+
256
+ When using `perform_in`:
257
+
258
+ ``` rb
259
+ expect {
260
+ call
261
+ }.to enqueue_sidekiq_robust_job(MyJob).with(user.id).in(5.seconds)
262
+ ```
263
+
264
+ When using `perform_at`:
265
+
266
+ ``` rb
267
+ expect {
268
+ call
269
+ }.to enqueue_sidekiq_robust_job(MyJob).with(user.id).at(5.seconds.from_now)
270
+ ```
271
+
272
+ ### How to migrate already enqueued jobs when introducing the gem?
273
+
274
+ This might be a bit tricky. You might consider using new job classes temporarily so that the already existing jobs are performed and the new ones are getting enqueued and then use again the original class with `SidekiqRobustJob::SidekiqJobExtensions` included and `call` method defined.
275
+
276
+ You can also stop workers, iterate over all existing jobs, re-schedule them (after including the module) and delete them - it's safe because the actual job will still be there, but it will be enqueued this time with job's ID and `call` method will be used. And you need to delete the original ones as they might have either a different `perform` method signature, or when having the same one, the argument will have a different meaning that job's ID, which can cause an unexpected behavior. This might require a downtime if you are not able to distinguish just based on the arguments of the job between the previous way of executing jobs and the new one. If you are able to, the downtime might not be required, but a lot of jobs can fail due to `perform` method's signature change. However, you can also re-enqueue these jobs and delete them from `RetrySet`.
277
+
278
+ ## Development
279
+
280
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
281
+
282
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
283
+
284
+ ## Contributing
285
+
286
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sidekiq-robust-job.
287
+
288
+
289
+ ## License
290
+
291
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).