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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c6d495a4455453f6f4af736d3fd31685fc2026c98e0cd564ea0f47b188cc473
4
- data.tar.gz: 794137e732ed3fcebec859daa8e58a2c2c8fd283cc43c5cfc7824263a1ca93fd
3
+ metadata.gz: b7b7100f183bea75e74273f8c60b87eb00f234b56196f1239ad5c1677e216250
4
+ data.tar.gz: b22120c6cdb91f38e201ef2fd8dd150f7775462ba27e7b9646bad504384c94d0
5
5
  SHA512:
6
- metadata.gz: 258757117262f25f5507ceb47c20f0f43a1f40427c7a0f9c3d0dc7803f25019062a160de08e96fd5ba74ceeeec5666d1084f6dff7ca9d381b2bbb198fabb10fd
7
- data.tar.gz: e2a5d09cfb57a7f2a08d59f84c3b39e3582cb5b0b76a7e6ea2da421542d5e0ebee2e25a79298c1f5d3351d58e156e43ac0770e1d9e26e0f26169620f0bfcc493
6
+ metadata.gz: '0844d72f1fb34e1608f40d6c134a3a24e8e5e0876ca4c96eaa246568db87f64b47be8170e5061517f455148da5e40fdd0a278a8a790a93a7ddb64eb024fdb35f'
7
+ data.tar.gz: 20934ee8ab6fc277519c2a551ee44d6803d09405215faff35fda4227e88729d720968235003e694f83887cea82709caa68eabc7afbb10335b2a671b3bddfe604
@@ -1,6 +1,19 @@
1
1
  # Changelog
2
2
 
3
- ## [v1.1.0](https://github.com/bensheldon/good_job/tree/v1.1.0) (2020-08-09)
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
- ```bash
76
- $ bundle exec good_job help start
75
+ ```bash
76
+ $ bundle exec good_job help start
77
77
 
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] # Queues to work from. Separate multiple queues with commas (default: *)
84
- # [--poll-interval=N] # Interval between polls for available jobs in seconds (default: 1)
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
- execution_mode = if execution_mode
6
- execution_mode
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
@@ -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'
@@ -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
- @execution_mode = :inline
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
- @scheduler = scheduler
18
- if @execution_mode == :async && @scheduler.blank? # rubocop:disable Style/GuardClause
19
- timer_options = {}
20
- timer_options[:execution_interval] = poll_interval if poll_interval.present?
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
- pool_options = {}
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
- job_performer = GoodJob::Performer.new(GoodJob::Job, :perform_with_advisory_lock, name: '*')
26
- @scheduler = GoodJob::Scheduler.new(job_performer, timer_options: timer_options, pool_options: pool_options)
27
- end
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)
@@ -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
- max_threads = (
22
- options[:max_threads] ||
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
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module GoodJob
2
- VERSION = '1.1.0'.freeze
2
+ VERSION = '1.1.1'.freeze
3
3
  end
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.0
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-10 00:00:00.000000000 Z
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