good_job 1.1.0 → 1.1.1
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/CHANGELOG.md +15 -6
- data/README.md +48 -10
- data/lib/active_job/queue_adapters/good_job_adapter.rb +3 -29
- data/lib/good_job.rb +2 -0
- data/lib/good_job/adapter.rb +13 -17
- data/lib/good_job/cli.rb +4 -31
- data/lib/good_job/configuration.rb +55 -0
- data/lib/good_job/log_subscriber.rb +1 -1
- data/lib/good_job/multi_scheduler.rb +25 -0
- data/lib/good_job/scheduler.rb +25 -0
- data/lib/good_job/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b7b7100f183bea75e74273f8c60b87eb00f234b56196f1239ad5c1677e216250
|
4
|
+
data.tar.gz: b22120c6cdb91f38e201ef2fd8dd150f7775462ba27e7b9646bad504384c94d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0844d72f1fb34e1608f40d6c134a3a24e8e5e0876ca4c96eaa246568db87f64b47be8170e5061517f455148da5e40fdd0a278a8a790a93a7ddb64eb024fdb35f'
|
7
|
+
data.tar.gz: 20934ee8ab6fc277519c2a551ee44d6803d09405215faff35fda4227e88729d720968235003e694f83887cea82709caa68eabc7afbb10335b2a671b3bddfe604
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## [v1.1.
|
3
|
+
## [v1.1.1](https://github.com/bensheldon/good_job/tree/v1.1.1) (2020-08-12)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.1.0...v1.1.1)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Allow multiple schedulers within the same process. e.g. `queues=mice:2,elephants:4` [\#45](https://github.com/bensheldon/good_job/issues/45)
|
10
|
+
|
11
|
+
**Merged pull requests:**
|
12
|
+
|
13
|
+
- Allow instantiation of multiple schedulers via --queues [\#76](https://github.com/bensheldon/good_job/pull/76) ([bensheldon](https://github.com/bensheldon))
|
14
|
+
- Extract options parsing to Configuration object [\#74](https://github.com/bensheldon/good_job/pull/74) ([bensheldon](https://github.com/bensheldon))
|
15
|
+
|
16
|
+
## [v1.1.0](https://github.com/bensheldon/good_job/tree/v1.1.0) (2020-08-10)
|
4
17
|
|
5
18
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.0.3...v1.1.0)
|
6
19
|
|
@@ -44,10 +57,6 @@
|
|
44
57
|
|
45
58
|
- Fix counting of available execution threads [\#58](https://github.com/bensheldon/good_job/pull/58) ([bensheldon](https://github.com/bensheldon))
|
46
59
|
|
47
|
-
**Closed issues:**
|
48
|
-
|
49
|
-
- repeating/recurring jobs [\#53](https://github.com/bensheldon/good_job/issues/53)
|
50
|
-
|
51
60
|
**Merged pull requests:**
|
52
61
|
|
53
62
|
- Add migration generator [\#56](https://github.com/bensheldon/good_job/pull/56) ([thedanbob](https://github.com/thedanbob))
|
@@ -115,6 +124,7 @@
|
|
115
124
|
- Add more examples to Readme [\#39](https://github.com/bensheldon/good_job/pull/39) ([bensheldon](https://github.com/bensheldon))
|
116
125
|
- Add additional Rubocops and lint [\#38](https://github.com/bensheldon/good_job/pull/38) ([bensheldon](https://github.com/bensheldon))
|
117
126
|
- Always store a default queue\_name, priority and scheduled\_at; index by queue\_name and scheduled\_at [\#37](https://github.com/bensheldon/good_job/pull/37) ([bensheldon](https://github.com/bensheldon))
|
127
|
+
- Extract Job querying behavior out of Scheduler [\#31](https://github.com/bensheldon/good_job/pull/31) ([bensheldon](https://github.com/bensheldon))
|
118
128
|
|
119
129
|
## [v0.6.0](https://github.com/bensheldon/good_job/tree/v0.6.0) (2020-07-15)
|
120
130
|
|
@@ -130,7 +140,6 @@
|
|
130
140
|
- Improve generation of changelog [\#36](https://github.com/bensheldon/good_job/pull/36) ([bensheldon](https://github.com/bensheldon))
|
131
141
|
- Update Github Action Workflow for Backlog Project Board [\#35](https://github.com/bensheldon/good_job/pull/35) ([bensheldon](https://github.com/bensheldon))
|
132
142
|
- Add configuration options to good\_job executable [\#33](https://github.com/bensheldon/good_job/pull/33) ([bensheldon](https://github.com/bensheldon))
|
133
|
-
- Extract Job querying behavior out of Scheduler [\#31](https://github.com/bensheldon/good_job/pull/31) ([bensheldon](https://github.com/bensheldon))
|
134
143
|
- Allow configuration of Rails queue adapter with `:good\_job` [\#28](https://github.com/bensheldon/good_job/pull/28) ([bensheldon](https://github.com/bensheldon))
|
135
144
|
|
136
145
|
## [v0.5.0](https://github.com/bensheldon/good_job/tree/v0.5.0) (2020-07-13)
|
data/README.md
CHANGED
@@ -72,17 +72,55 @@ $ bundle install
|
|
72
72
|
|
73
73
|
Configuration options available with `help`:
|
74
74
|
|
75
|
-
|
76
|
-
|
75
|
+
```bash
|
76
|
+
$ bundle exec good_job help start
|
77
77
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
78
|
+
Usage:
|
79
|
+
good_job start
|
80
|
+
|
81
|
+
Options:
|
82
|
+
[--max-threads=N] # Maximum number of threads to use for working jobs (default: ActiveRecord::Base.connection_pool.size)
|
83
|
+
[--queues=queue1,queue2(;queue3,queue4:5)] # Queues to work from. Separate multiple queues with commas; separate isolated execution pools with semicolons and threads with colons (default: *)
|
84
|
+
[--poll-interval=N] # Interval between polls for available jobs in seconds (default: 1)
|
85
|
+
|
86
|
+
Start job worker
|
87
|
+
```
|
88
|
+
|
89
|
+
1. Optimize execution to reduce congestion and execution latency. By default, GoodJob creates a single thread execution pool that will execute jobs from any queue. Depending on your application's workload, job types, and service level objectives, you may wish to optimize execution resources; for example, providing dedicated execution resources for transactional emails so they are not delayed by long-running batch jobs. Some options:
|
90
|
+
|
91
|
+
- Multiple execution pools within a single process:
|
92
|
+
|
93
|
+
```bash
|
94
|
+
$ bundle exec good_job --queues=*;transactional_messages:2;batch_processing:1 --max-threads=5
|
95
|
+
```
|
96
|
+
|
97
|
+
This configuration will result in a single process with 3 isolated thread execution pools. A pool that will run jobs from any queue, `*`, with up to 5 threads; a pool that will only run jobs enqueued on `transactional_messages` with up to 2 threads; and a pool dedicated to the `batch_processing` queue with a single thread.
|
98
|
+
|
99
|
+
For moderate workloads, multiple isolated thread execution pools offers a good balance between congestion management and economy.
|
100
|
+
|
101
|
+
Configuration can be injected by environment variables too:
|
102
|
+
|
103
|
+
```bash
|
104
|
+
$ GOOD_JOB_QUEUES="*;transactional_messages:2;batch_processing:1" GOOD_JOB_MAX_THREADS=5 bundle exec good_job
|
105
|
+
```
|
106
|
+
|
107
|
+
- Multiple processes; for example, on Heroku:
|
108
|
+
|
109
|
+
```procfile
|
110
|
+
# Procfile
|
111
|
+
|
112
|
+
# Separate dyno types
|
113
|
+
worker: bundle exec good_job --max-threads=5
|
114
|
+
transactional_worker: bundle exec good_job --queues=transactional_messages --max-threads=2
|
115
|
+
batch_worker: bundle exec good_job --queues=batch_processing --max-threads=1
|
116
|
+
|
117
|
+
# Combined multi-process dyno
|
118
|
+
combined_worker: bundle exec good_job --max-threads=5 & bundle exec good_job --queues=transactional_messages --max-threads=2 & bundle exec good_job --queues=batch_processing --max-threads=1 & wait -n
|
119
|
+
```
|
120
|
+
|
121
|
+
Running multiple processes can optimize for CPU performance at the expense of greater memory and system resource usage.
|
122
|
+
|
123
|
+
_Keep in mind, queue operations and management is an advanced discipline. This stuff is complex, especially for heavy workloads and unique processing requirements. Good job 👍_
|
86
124
|
|
87
125
|
### Error handling, retries, and reliability
|
88
126
|
|
@@ -1,35 +1,9 @@
|
|
1
1
|
module ActiveJob
|
2
2
|
module QueueAdapters
|
3
3
|
class GoodJobAdapter < GoodJob::Adapter
|
4
|
-
def initialize(execution_mode: nil, max_threads: nil, poll_interval: nil, scheduler: nil)
|
5
|
-
|
6
|
-
|
7
|
-
elsif ENV['GOOD_JOB_EXECUTION_MODE'].present?
|
8
|
-
ENV['GOOD_JOB_EXECUTION_MODE'].to_sym
|
9
|
-
elsif Rails.env.development?
|
10
|
-
:inline
|
11
|
-
elsif Rails.env.test?
|
12
|
-
:inline
|
13
|
-
else
|
14
|
-
:external
|
15
|
-
end
|
16
|
-
|
17
|
-
if execution_mode == :async && scheduler.blank?
|
18
|
-
max_threads = (
|
19
|
-
max_threads.presence ||
|
20
|
-
ENV['GOOD_JOB_MAX_THREADS'] ||
|
21
|
-
ENV['RAILS_MAX_THREADS'] ||
|
22
|
-
ActiveRecord::Base.connection_pool.size
|
23
|
-
).to_i
|
24
|
-
|
25
|
-
poll_interval = (
|
26
|
-
poll_interval.presence ||
|
27
|
-
ENV['GOOD_JOB_POLL_INTERVAL'] ||
|
28
|
-
1
|
29
|
-
).to_i
|
30
|
-
end
|
31
|
-
|
32
|
-
super(execution_mode: execution_mode, max_threads: max_threads, poll_interval: poll_interval, scheduler: scheduler)
|
4
|
+
def initialize(execution_mode: nil, max_threads: nil, poll_interval: nil, scheduler: nil, inline: false)
|
5
|
+
configuration = GoodJob::Configuration.new({ execution_mode: execution_mode }, env: ENV)
|
6
|
+
super(execution_mode: configuration.rails_execution_mode, max_threads: max_threads, poll_interval: poll_interval, scheduler: scheduler, inline: inline)
|
33
7
|
end
|
34
8
|
end
|
35
9
|
end
|
data/lib/good_job.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require "rails"
|
2
2
|
require 'good_job/railtie'
|
3
3
|
|
4
|
+
require 'good_job/configuration'
|
4
5
|
require 'good_job/log_subscriber'
|
5
6
|
require 'good_job/lockable'
|
6
7
|
require 'good_job/job'
|
7
8
|
require 'good_job/scheduler'
|
9
|
+
require 'good_job/multi_scheduler'
|
8
10
|
require 'good_job/adapter'
|
9
11
|
require 'good_job/pg_locks'
|
10
12
|
require 'good_job/performer'
|
data/lib/good_job/adapter.rb
CHANGED
@@ -3,28 +3,24 @@ module GoodJob
|
|
3
3
|
EXECUTION_MODES = [:async, :external, :inline].freeze
|
4
4
|
|
5
5
|
def initialize(execution_mode: nil, max_threads: nil, poll_interval: nil, scheduler: nil, inline: false)
|
6
|
-
if inline
|
6
|
+
if inline && execution_mode.nil?
|
7
7
|
ActiveSupport::Deprecation.warn('GoodJob::Adapter#new(inline: true) is deprecated; use GoodJob::Adapter.new(execution_mode: :inline) instead')
|
8
|
-
|
9
|
-
elsif execution_mode
|
10
|
-
raise ArgumentError, "execution_mode: must be one of #{EXECUTION_MODES.join(', ')}." unless EXECUTION_MODES.include?(execution_mode)
|
11
|
-
|
12
|
-
@execution_mode = execution_mode
|
13
|
-
else
|
14
|
-
@execution_mode = :external
|
8
|
+
execution_mode = :inline
|
15
9
|
end
|
16
10
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
11
|
+
configuration = GoodJob::Configuration.new({
|
12
|
+
execution_mode: execution_mode,
|
13
|
+
max_threads: max_threads,
|
14
|
+
poll_interval: poll_interval,
|
15
|
+
},
|
16
|
+
env: ENV)
|
21
17
|
|
22
|
-
|
23
|
-
pool_options[:max_threads] = max_threads if max_threads.present?
|
18
|
+
raise ArgumentError, "execution_mode: must be one of #{EXECUTION_MODES.join(', ')}." unless EXECUTION_MODES.include?(configuration.execution_mode)
|
24
19
|
|
25
|
-
|
26
|
-
|
27
|
-
|
20
|
+
@execution_mode = configuration.execution_mode
|
21
|
+
|
22
|
+
@scheduler = scheduler
|
23
|
+
@scheduler = GoodJob::Scheduler.from_configuration(configuration) if @execution_mode == :async && @scheduler.blank?
|
28
24
|
end
|
29
25
|
|
30
26
|
def enqueue(active_job)
|
data/lib/good_job/cli.rb
CHANGED
@@ -10,43 +10,16 @@ module GoodJob
|
|
10
10
|
desc: "Maximum number of threads to use for working jobs (default: ActiveRecord::Base.connection_pool.size)"
|
11
11
|
method_option :queues,
|
12
12
|
type: :string,
|
13
|
-
banner: "queue1,queue2",
|
14
|
-
desc: "Queues to work from. Separate multiple queues with commas (default: *)"
|
13
|
+
banner: "queue1,queue2(;queue3,queue4:5)",
|
14
|
+
desc: "Queues to work from. Separate multiple queues with commas; separate isolated execution pools with semicolons and threads with colons (default: *)"
|
15
15
|
method_option :poll_interval,
|
16
16
|
type: :numeric,
|
17
17
|
desc: "Interval between polls for available jobs in seconds (default: 1)"
|
18
18
|
def start
|
19
19
|
set_up_application!
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
ENV['GOOD_JOB_MAX_THREADS'] ||
|
24
|
-
ENV['RAILS_MAX_THREADS'] ||
|
25
|
-
ActiveRecord::Base.connection_pool.size
|
26
|
-
).to_i
|
27
|
-
|
28
|
-
queue_string = (
|
29
|
-
options[:queues] ||
|
30
|
-
ENV['GOOD_JOB_QUEUES'] ||
|
31
|
-
'*'
|
32
|
-
)
|
33
|
-
|
34
|
-
poll_interval = (
|
35
|
-
options[:poll_interval] ||
|
36
|
-
ENV['GOOD_JOB_POLL_INTERVAL']
|
37
|
-
).to_i
|
38
|
-
|
39
|
-
job_query = GoodJob::Job.queue_string(queue_string)
|
40
|
-
job_performer = GoodJob::Performer.new(job_query, :perform_with_advisory_lock, name: queue_string)
|
41
|
-
|
42
|
-
timer_options = {}
|
43
|
-
timer_options[:execution_interval] = poll_interval if poll_interval.positive?
|
44
|
-
|
45
|
-
pool_options = {
|
46
|
-
max_threads: max_threads,
|
47
|
-
}
|
48
|
-
|
49
|
-
scheduler = GoodJob::Scheduler.new(job_performer, timer_options: timer_options, pool_options: pool_options)
|
21
|
+
configuration = Configuration.new(options, env: ENV)
|
22
|
+
scheduler = Scheduler.from_configuration(configuration)
|
50
23
|
|
51
24
|
@stop_good_job_executable = false
|
52
25
|
%w[INT TERM].each do |signal|
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module GoodJob
|
2
|
+
class Configuration
|
3
|
+
attr_reader :options, :env
|
4
|
+
|
5
|
+
def initialize(options, env: ENV)
|
6
|
+
@options = options
|
7
|
+
@env = env
|
8
|
+
end
|
9
|
+
|
10
|
+
def execution_mode(default: :external)
|
11
|
+
if options[:execution_mode]
|
12
|
+
options[:execution_mode]
|
13
|
+
elsif env['GOOD_JOB_EXECUTION_MODE'].present?
|
14
|
+
env['GOOD_JOB_EXECUTION_MODE'].to_sym
|
15
|
+
else
|
16
|
+
default
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def rails_execution_mode
|
21
|
+
if execution_mode(default: nil)
|
22
|
+
execution_mode
|
23
|
+
elsif Rails.env.development?
|
24
|
+
:inline
|
25
|
+
elsif Rails.env.test?
|
26
|
+
:inline
|
27
|
+
else
|
28
|
+
:external
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def max_threads
|
33
|
+
(
|
34
|
+
options[:max_threads] ||
|
35
|
+
env['GOOD_JOB_MAX_THREADS'] ||
|
36
|
+
env['RAILS_MAX_THREADS'] ||
|
37
|
+
ActiveRecord::Base.connection_pool.size
|
38
|
+
).to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
def queue_string
|
42
|
+
options[:queues] ||
|
43
|
+
env['GOOD_JOB_QUEUES'] ||
|
44
|
+
'*'
|
45
|
+
end
|
46
|
+
|
47
|
+
def poll_interval
|
48
|
+
(
|
49
|
+
options[:poll_interval] ||
|
50
|
+
env['GOOD_JOB_POLL_INTERVAL'] ||
|
51
|
+
1
|
52
|
+
).to_i
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -96,7 +96,7 @@ module GoodJob
|
|
96
96
|
def info_and_stdout(progname = nil, tags: [], &block)
|
97
97
|
unless ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT)
|
98
98
|
tags_string = (['GoodJob'] + tags).map { |t| "[#{t}]" }.join(' ')
|
99
|
-
stdout_message = "#{tags_string}#{yield}"
|
99
|
+
stdout_message = "#{tags_string} #{yield}"
|
100
100
|
$stdout.puts stdout_message
|
101
101
|
end
|
102
102
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module GoodJob
|
2
|
+
class MultiScheduler
|
3
|
+
attr_reader :schedulers
|
4
|
+
|
5
|
+
def initialize(schedulers)
|
6
|
+
@schedulers = schedulers
|
7
|
+
end
|
8
|
+
|
9
|
+
def shutdown(wait: true)
|
10
|
+
schedulers.each { |s| s.shutdown(wait: wait) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def shutdown?
|
14
|
+
schedulers.all?(&:shutdown?)
|
15
|
+
end
|
16
|
+
|
17
|
+
def restart(wait: true)
|
18
|
+
schedulers.each { |s| s.restart(wait: wait) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_thread
|
22
|
+
schedulers.all?(&:create_thread)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -22,6 +22,31 @@ module GoodJob
|
|
22
22
|
|
23
23
|
cattr_reader :instances, default: [], instance_reader: false
|
24
24
|
|
25
|
+
def self.from_configuration(configuration)
|
26
|
+
schedulers = configuration.queue_string.split(';').map do |queue_string_and_max_threads|
|
27
|
+
queue_string, max_threads = queue_string_and_max_threads.split(':')
|
28
|
+
max_threads = (max_threads || configuration.max_threads).to_i
|
29
|
+
|
30
|
+
job_query = GoodJob::Job.queue_string(queue_string)
|
31
|
+
job_performer = GoodJob::Performer.new(job_query, :perform_with_advisory_lock, name: queue_string)
|
32
|
+
|
33
|
+
timer_options = {}
|
34
|
+
timer_options[:execution_interval] = configuration.poll_interval if configuration.poll_interval.positive?
|
35
|
+
|
36
|
+
pool_options = {
|
37
|
+
max_threads: max_threads,
|
38
|
+
}
|
39
|
+
|
40
|
+
GoodJob::Scheduler.new(job_performer, timer_options: timer_options, pool_options: pool_options)
|
41
|
+
end
|
42
|
+
|
43
|
+
if schedulers.size > 1
|
44
|
+
GoodJob::MultiScheduler.new(schedulers)
|
45
|
+
else
|
46
|
+
schedulers.first
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
25
50
|
def initialize(performer, timer_options: {}, pool_options: {})
|
26
51
|
raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
|
27
52
|
|
data/lib/good_job/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: good_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-08-
|
11
|
+
date: 2020-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -269,9 +269,11 @@ files:
|
|
269
269
|
- lib/good_job.rb
|
270
270
|
- lib/good_job/adapter.rb
|
271
271
|
- lib/good_job/cli.rb
|
272
|
+
- lib/good_job/configuration.rb
|
272
273
|
- lib/good_job/job.rb
|
273
274
|
- lib/good_job/lockable.rb
|
274
275
|
- lib/good_job/log_subscriber.rb
|
276
|
+
- lib/good_job/multi_scheduler.rb
|
275
277
|
- lib/good_job/performer.rb
|
276
278
|
- lib/good_job/pg_locks.rb
|
277
279
|
- lib/good_job/railtie.rb
|