rocketjob 6.3.2 → 7.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 +4 -4
- data/README.md +38 -316
- data/bin/rocketjob_batch_perf +1 -1
- data/lib/rocket_job/batch/categories.rb +39 -26
- data/lib/rocket_job/batch/io.rb +53 -22
- data/lib/rocket_job/batch/logger.rb +2 -2
- data/lib/rocket_job/batch/model.rb +2 -2
- data/lib/rocket_job/batch/performance.rb +1 -1
- data/lib/rocket_job/batch/statistics.rb +9 -2
- data/lib/rocket_job/batch/throttle.rb +7 -2
- data/lib/rocket_job/batch/throttle_running_workers.rb +5 -1
- data/lib/rocket_job/batch/throttle_windows.rb +4 -2
- data/lib/rocket_job/batch/worker.rb +51 -11
- data/lib/rocket_job/category/base.rb +119 -1
- data/lib/rocket_job/category/input.rb +166 -40
- data/lib/rocket_job/category/output.rb +7 -0
- data/lib/rocket_job/config.rb +6 -1
- data/lib/rocket_job/deprecation.rb +51 -0
- data/lib/rocket_job/dirmon_entry.rb +6 -4
- data/lib/rocket_job/event.rb +84 -4
- data/lib/rocket_job/extensions/mongo/logging.rb +1 -0
- data/lib/rocket_job/extensions/mongoid/clients/options.rb +3 -3
- data/lib/rocket_job/extensions/mongoid/contextual/mongo.rb +3 -4
- data/lib/rocket_job/extensions/mongoid/factory.rb +2 -2
- data/lib/rocket_job/extensions/psych/yaml_tree.rb +8 -4
- data/lib/rocket_job/extensions/rocket_job_adapter.rb +1 -1
- data/lib/rocket_job/jobs/active_job.rb +1 -1
- data/lib/rocket_job/jobs/copy_file_job.rb +11 -1
- data/lib/rocket_job/jobs/dirmon_job.rb +3 -1
- data/lib/rocket_job/jobs/on_demand_batch_job.rb +1 -1
- data/lib/rocket_job/jobs/upload_file_job.rb +8 -5
- data/lib/rocket_job/lookup_collection.rb +2 -2
- data/lib/rocket_job/performance.rb +1 -1
- data/lib/rocket_job/plugins/cron.rb +6 -3
- data/lib/rocket_job/plugins/document.rb +0 -2
- data/lib/rocket_job/plugins/job/logger.rb +2 -2
- data/lib/rocket_job/plugins/job/model.rb +1 -1
- data/lib/rocket_job/plugins/job/persistence.rb +2 -2
- data/lib/rocket_job/plugins/job/throttle.rb +14 -2
- data/lib/rocket_job/plugins/job/throttle_running_jobs.rb +4 -1
- data/lib/rocket_job/plugins/job/worker.rb +5 -2
- data/lib/rocket_job/plugins/retry.rb +36 -35
- data/lib/rocket_job/plugins/throttle_dependent_jobs.rb +7 -2
- data/lib/rocket_job/plugins/transaction.rb +2 -2
- data/lib/rocket_job/sliced/input.rb +17 -12
- data/lib/rocket_job/sliced/slice.rb +3 -3
- data/lib/rocket_job/sliced/slices.rb +3 -6
- data/lib/rocket_job/sliced/writer/input.rb +4 -4
- data/lib/rocket_job/supervisor.rb +2 -2
- data/lib/rocket_job/thread_worker.rb +3 -3
- data/lib/rocket_job/throttle_definition.rb +19 -2
- data/lib/rocket_job/throttle_definitions.rb +14 -9
- data/lib/rocket_job/version.rb +1 -1
- data/lib/rocket_job/worker.rb +77 -28
- data/lib/rocketjob.rb +2 -2
- metadata +34 -27
- data/lib/rocket_job/ractor_worker.rb +0 -42
data/lib/rocket_job/worker.rb
CHANGED
|
@@ -63,25 +63,29 @@ module RocketJob
|
|
|
63
63
|
logger.info "Started"
|
|
64
64
|
|
|
65
65
|
until shutdown?
|
|
66
|
-
sleep_seconds = Config.max_poll_seconds
|
|
67
66
|
reset_filter_if_expired
|
|
68
67
|
job = next_available_job
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
if job
|
|
70
|
+
# Returns true when this job has no more work immediately available,
|
|
71
|
+
# for example a batch job that has run out of slices, or was throttled.
|
|
72
|
+
no_immediate_work = job.rocket_job_work(self, false)
|
|
73
|
+
|
|
74
|
+
# Return the database connections for this thread back to the connection pool.
|
|
75
|
+
release_active_record_connections
|
|
76
|
+
|
|
77
|
+
sleep_seconds =
|
|
78
|
+
if no_immediate_work
|
|
79
|
+
# Stagger workers so that they don't all poll at the same time.
|
|
80
|
+
random_wait_interval
|
|
77
81
|
else
|
|
78
|
-
|
|
82
|
+
# Work was performed and more queued work is likely available,
|
|
83
|
+
# so poll again immediately rather than waiting a full interval.
|
|
84
|
+
0
|
|
79
85
|
end
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
# Stagger workers so that they don't all poll at the same time.
|
|
84
|
-
sleep_seconds = random_wait_interval
|
|
86
|
+
else
|
|
87
|
+
# No work was found, so wait the full poll interval before checking again.
|
|
88
|
+
sleep_seconds = Config.max_poll_seconds
|
|
85
89
|
end
|
|
86
90
|
|
|
87
91
|
wait_for_shutdown?(sleep_seconds)
|
|
@@ -91,12 +95,18 @@ module RocketJob
|
|
|
91
95
|
rescue Exception => e
|
|
92
96
|
logger.fatal("Unhandled exception in job processing thread", e)
|
|
93
97
|
ensure
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
release_active_record_connections
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Return the database connections for this thread back to the connection pool.
|
|
102
|
+
def release_active_record_connections
|
|
103
|
+
return unless defined?(ActiveRecord::Base)
|
|
104
|
+
|
|
105
|
+
if ActiveRecord::Base.respond_to?(:connection_handler)
|
|
106
|
+
# Rails 7.2+
|
|
107
|
+
ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
|
|
108
|
+
else
|
|
109
|
+
ActiveRecord::Base.clear_active_connections!
|
|
100
110
|
end
|
|
101
111
|
end
|
|
102
112
|
|
|
@@ -149,12 +159,19 @@ module RocketJob
|
|
|
149
159
|
# Whether the supplied job has been throttled and should be ignored.
|
|
150
160
|
def throttled_job?(job)
|
|
151
161
|
# Evaluate job throttles, if any.
|
|
152
|
-
|
|
153
|
-
return false unless
|
|
154
|
-
|
|
155
|
-
add_to_current_filter(
|
|
156
|
-
# Restore retrieved job so that other workers can process it later
|
|
157
|
-
|
|
162
|
+
throttle = job.rocket_job_throttles.matching_throttle(job)
|
|
163
|
+
return false unless throttle
|
|
164
|
+
|
|
165
|
+
add_to_current_filter(throttle.extract_filter(job))
|
|
166
|
+
# Restore retrieved job so that other workers can process it later, recording
|
|
167
|
+
# why it was throttled so it is visible in Mission Control. This reuses the
|
|
168
|
+
# write that requeues the job, so it adds no extra database round trip.
|
|
169
|
+
job.set(
|
|
170
|
+
worker_name: nil,
|
|
171
|
+
state: :queued,
|
|
172
|
+
throttled_by: throttle.extract_description(job),
|
|
173
|
+
throttled_at: Time.now
|
|
174
|
+
)
|
|
158
175
|
true
|
|
159
176
|
end
|
|
160
177
|
|
|
@@ -164,14 +181,46 @@ module RocketJob
|
|
|
164
181
|
# Applies the current filter to exclude filtered jobs.
|
|
165
182
|
#
|
|
166
183
|
# Returns nil if no jobs are available for processing.
|
|
184
|
+
#
|
|
185
|
+
# Notes:
|
|
186
|
+
# - An already running batch job is _joined_ with a read-only query, since
|
|
187
|
+
# concurrency for a batch job is coordinated per-slice (see
|
|
188
|
+
# `Sliced::Input#next_slice`), not on the job document. Writing
|
|
189
|
+
# `worker_name`/`state` to the shared job document on every poll turns it
|
|
190
|
+
# into a write-contention hotspot: with many workers MongoDB serializes the
|
|
191
|
+
# updates and retries the write conflicts (server log id 46404), which is
|
|
192
|
+
# what makes a large batch job slow down as workers are added.
|
|
193
|
+
# - Only a queued job is claimed with a write, transitioning it to running.
|
|
194
|
+
# That write is naturally distributed: each queued job is claimed once.
|
|
167
195
|
def find_and_assign_job
|
|
168
196
|
SemanticLogger.silence(:info) do
|
|
169
197
|
scheduled = RocketJob::Job.where(run_at: nil).or(:run_at.lte => Time.now)
|
|
170
198
|
working = RocketJob::Job.queued.or(state: "running", sub_state: "processing")
|
|
171
199
|
query = RocketJob::Job.and(working, scheduled)
|
|
172
200
|
query = query.and(current_filter) unless current_filter.blank?
|
|
173
|
-
|
|
174
|
-
|
|
201
|
+
query = query.sort(priority: 1, _id: 1)
|
|
202
|
+
|
|
203
|
+
# Retry only when a queued job was claimed by another worker first.
|
|
204
|
+
loop do
|
|
205
|
+
job = query.first
|
|
206
|
+
return nil unless job
|
|
207
|
+
|
|
208
|
+
# Already running batch job: join it without writing to the job document.
|
|
209
|
+
return job if job.running?
|
|
210
|
+
|
|
211
|
+
# Queued job: claim it atomically, guarding on it still being queued.
|
|
212
|
+
# find_one_and_update returns the pre-image (state: queued) so that the
|
|
213
|
+
# caller still runs throttles and `start!`.
|
|
214
|
+
# Clear any stale throttle reason from a previous throttled poll so a job
|
|
215
|
+
# that is now allowed to run no longer reports as throttled in Mission Control.
|
|
216
|
+
update = {
|
|
217
|
+
"$set" => {"worker_name" => name, "state" => "running"},
|
|
218
|
+
"$unset" => {"throttled_by" => "", "throttled_at" => ""}
|
|
219
|
+
}
|
|
220
|
+
claimed = RocketJob::Job.queued.where(id: job.id).
|
|
221
|
+
find_one_and_update(update, bypass_document_validation: true)
|
|
222
|
+
return claimed if claimed
|
|
223
|
+
end
|
|
175
224
|
end
|
|
176
225
|
end
|
|
177
226
|
|
data/lib/rocketjob.rb
CHANGED
|
@@ -23,6 +23,7 @@ module RocketJob
|
|
|
23
23
|
autoload :ActiveWorker, "rocket_job/active_worker"
|
|
24
24
|
autoload :Batch, "rocket_job/batch"
|
|
25
25
|
autoload :CLI, "rocket_job/cli"
|
|
26
|
+
autoload :Deprecation, "rocket_job/deprecation"
|
|
26
27
|
autoload :DirmonEntry, "rocket_job/dirmon_entry"
|
|
27
28
|
autoload :Event, "rocket_job/event"
|
|
28
29
|
autoload :Heartbeat, "rocket_job/heartbeat"
|
|
@@ -31,7 +32,6 @@ module RocketJob
|
|
|
31
32
|
autoload :LookupCollection, "rocket_job/lookup_collection"
|
|
32
33
|
autoload :Worker, "rocket_job/worker"
|
|
33
34
|
autoload :Performance, "rocket_job/performance"
|
|
34
|
-
autoload :RactorWorker, "rocket_job/ractor_worker"
|
|
35
35
|
autoload :Server, "rocket_job/server"
|
|
36
36
|
autoload :Sliced, "rocket_job/sliced"
|
|
37
37
|
autoload :Subscriber, "rocket_job/subscriber"
|
|
@@ -83,7 +83,7 @@ module RocketJob
|
|
|
83
83
|
autoload :UploadFileJob, "rocket_job/jobs/upload_file_job"
|
|
84
84
|
|
|
85
85
|
module ReEncrypt
|
|
86
|
-
autoload :RelationalJob,
|
|
86
|
+
autoload :RelationalJob, "rocket_job/jobs/re_encrypt/relational_job"
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rocketjob
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 7.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Reid Morrison
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: aasm
|
|
@@ -56,62 +55,65 @@ dependencies:
|
|
|
56
55
|
name: iostreams
|
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
|
58
57
|
requirements:
|
|
59
|
-
- - "
|
|
58
|
+
- - "~>"
|
|
60
59
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
60
|
+
version: '2.2'
|
|
62
61
|
type: :runtime
|
|
63
62
|
prerelease: false
|
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
64
|
requirements:
|
|
66
|
-
- - "
|
|
65
|
+
- - "~>"
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
67
|
+
version: '2.2'
|
|
69
68
|
- !ruby/object:Gem::Dependency
|
|
70
69
|
name: mongoid
|
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
|
72
71
|
requirements:
|
|
73
72
|
- - ">="
|
|
74
73
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
74
|
+
version: '8.1'
|
|
76
75
|
type: :runtime
|
|
77
76
|
prerelease: false
|
|
78
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
78
|
requirements:
|
|
80
79
|
- - ">="
|
|
81
80
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
81
|
+
version: '8.1'
|
|
83
82
|
- !ruby/object:Gem::Dependency
|
|
84
83
|
name: semantic_logger
|
|
85
84
|
requirement: !ruby/object:Gem::Requirement
|
|
86
85
|
requirements:
|
|
87
|
-
- - "
|
|
86
|
+
- - "~>"
|
|
88
87
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
88
|
+
version: '5.0'
|
|
90
89
|
type: :runtime
|
|
91
90
|
prerelease: false
|
|
92
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
92
|
requirements:
|
|
94
|
-
- - "
|
|
93
|
+
- - "~>"
|
|
95
94
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '
|
|
95
|
+
version: '5.0'
|
|
97
96
|
- !ruby/object:Gem::Dependency
|
|
98
97
|
name: symmetric-encryption
|
|
99
98
|
requirement: !ruby/object:Gem::Requirement
|
|
100
99
|
requirements:
|
|
101
|
-
- - "
|
|
100
|
+
- - "~>"
|
|
102
101
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '4.
|
|
102
|
+
version: '4.6'
|
|
104
103
|
type: :runtime
|
|
105
104
|
prerelease: false
|
|
106
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
106
|
requirements:
|
|
108
|
-
- - "
|
|
107
|
+
- - "~>"
|
|
109
108
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '4.
|
|
111
|
-
description:
|
|
112
|
-
|
|
109
|
+
version: '4.6'
|
|
110
|
+
description: Rocket Job is a distributed, priority-based, MongoDB-backed batch processing
|
|
111
|
+
system for Ruby. Run conventional background jobs, or split a single job's input
|
|
112
|
+
into slices and process it concurrently across thousands of workers, spilling from
|
|
113
|
+
memory to disk so very large files never fall over.
|
|
113
114
|
executables:
|
|
114
115
|
- rocketjob
|
|
116
|
+
- rocketjob_batch_perf
|
|
115
117
|
- rocketjob_perf
|
|
116
118
|
extensions: []
|
|
117
119
|
extra_rdoc_files: []
|
|
@@ -143,6 +145,7 @@ files:
|
|
|
143
145
|
- lib/rocket_job/category/output.rb
|
|
144
146
|
- lib/rocket_job/cli.rb
|
|
145
147
|
- lib/rocket_job/config.rb
|
|
148
|
+
- lib/rocket_job/deprecation.rb
|
|
146
149
|
- lib/rocket_job/dirmon_entry.rb
|
|
147
150
|
- lib/rocket_job/event.rb
|
|
148
151
|
- lib/rocket_job/extensions/iostreams/path.rb
|
|
@@ -186,7 +189,6 @@ files:
|
|
|
186
189
|
- lib/rocket_job/plugins/state_machine.rb
|
|
187
190
|
- lib/rocket_job/plugins/throttle_dependent_jobs.rb
|
|
188
191
|
- lib/rocket_job/plugins/transaction.rb
|
|
189
|
-
- lib/rocket_job/ractor_worker.rb
|
|
190
192
|
- lib/rocket_job/railtie.rb
|
|
191
193
|
- lib/rocket_job/rocket_job.rb
|
|
192
194
|
- lib/rocket_job/server.rb
|
|
@@ -217,11 +219,16 @@ files:
|
|
|
217
219
|
- lib/rocket_job/worker.rb
|
|
218
220
|
- lib/rocket_job/worker_pool.rb
|
|
219
221
|
- lib/rocketjob.rb
|
|
220
|
-
homepage: https://rocketjob.
|
|
222
|
+
homepage: https://rocketjob.reidmorrison.com
|
|
221
223
|
licenses:
|
|
222
224
|
- Apache-2.0
|
|
223
|
-
metadata:
|
|
224
|
-
|
|
225
|
+
metadata:
|
|
226
|
+
bug_tracker_uri: https://github.com/reidmorrison/rocketjob/issues
|
|
227
|
+
changelog_uri: https://github.com/reidmorrison/rocketjob/blob/main/CHANGELOG.md
|
|
228
|
+
documentation_uri: https://rocketjob.reidmorrison.com
|
|
229
|
+
homepage_uri: https://rocketjob.reidmorrison.com
|
|
230
|
+
source_code_uri: https://github.com/reidmorrison/rocketjob/tree/v7.1.0
|
|
231
|
+
rubygems_mfa_required: 'true'
|
|
225
232
|
rdoc_options: []
|
|
226
233
|
require_paths:
|
|
227
234
|
- lib
|
|
@@ -229,15 +236,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
229
236
|
requirements:
|
|
230
237
|
- - ">="
|
|
231
238
|
- !ruby/object:Gem::Version
|
|
232
|
-
version:
|
|
239
|
+
version: 3.2.0
|
|
233
240
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
234
241
|
requirements:
|
|
235
242
|
- - ">="
|
|
236
243
|
- !ruby/object:Gem::Version
|
|
237
244
|
version: '0'
|
|
238
245
|
requirements: []
|
|
239
|
-
rubygems_version: 3.
|
|
240
|
-
signing_key:
|
|
246
|
+
rubygems_version: 3.6.9
|
|
241
247
|
specification_version: 4
|
|
242
|
-
summary:
|
|
248
|
+
summary: Process millions of records across thousands of workers. A distributed, MongoDB-backed
|
|
249
|
+
batch processing system for Ruby.
|
|
243
250
|
test_files: []
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
module RocketJob
|
|
2
|
-
# Run each worker in its own "Ractor".
|
|
3
|
-
class RactorWorker < Worker
|
|
4
|
-
attr_reader :thread
|
|
5
|
-
|
|
6
|
-
def initialize(id:, server_name:)
|
|
7
|
-
super(id: id, server_name: server_name)
|
|
8
|
-
@shutdown = Concurrent::Event.new
|
|
9
|
-
@thread = Ractor.new(name: "rocketjob-#{id}") { run }
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def alive?
|
|
13
|
-
@thread.alive?
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def backtrace
|
|
17
|
-
@thread.backtrace
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def join(*args)
|
|
21
|
-
@thread.join(*args)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Send each active worker the RocketJob::ShutdownException so that stops processing immediately.
|
|
25
|
-
def kill
|
|
26
|
-
@thread.raise(Shutdown, "Shutdown due to kill request for worker: #{name}") if @thread.alive?
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def shutdown?
|
|
30
|
-
@shutdown.set?
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def shutdown!
|
|
34
|
-
@shutdown.set
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Returns [true|false] whether the shutdown indicator was set
|
|
38
|
-
def wait_for_shutdown?(timeout = nil)
|
|
39
|
-
@shutdown.wait(timeout)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|