Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow named queues to be excluded with a minus #77

Merged
merged 1 commit into from
Aug 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,29 +79,36 @@ $ bundle install
good_job start

Options:
[--max-threads=N] # Maximum number of threads to use for working jobs (default: ActiveRecord::Base.connection_pool.size)
[--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: *)
[--poll-interval=N] # Interval between polls for available jobs in seconds (default: 1)
[--max-threads=N] # Maximum number of threads to use for working jobs (default: ActiveRecord::Base.connection_pool.size)
[--queues=queue1,queue2(;queue3,queue4:5;-queue1,queue2)] # Queues to work from. Separate multiple queues with commas; exclude queues with a leading minus; separate isolated execution pools with semicolons and threads with colons (default: *)
[--poll-interval=N] # Interval between polls for available jobs in seconds (default: 1)

Start job worker
```

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:
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:

- Multiple execution pools within a single process:

```bash
$ bundle exec good_job --queues=*;transactional_messages:2;batch_processing:1 --max-threads=5
$ bundle exec good_job --queues=transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;* --max-threads=5
```

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.
This configuration will result in a single process with 4 isolated thread execution pools. Isolated execution pools are separated with a semicolon (`;`) and queue names and thread counts with a colon (`:`)

- `transactional_messages:2`: execute jobs enqueued on `transactional_messages` with up to 2 threads.
- `batch_processing:1` execute jobs enqueued on `batch_processing` with a single thread.
- `-transactional_messages,batch_processing`: execute jobs enqueued on _any_ queue _excluding_ `transactional_messages` or `batch_processing` with up to 2 threads.
- `*`: execute jobs on any queue on up to 5 threads, as configured by `--max-threads=5`

For moderate workloads, multiple isolated thread execution pools offers a good balance between congestion management and economy.

Configuration can be injected by environment variables too:

```bash
$ GOOD_JOB_QUEUES="*;transactional_messages:2;batch_processing:1" GOOD_JOB_MAX_THREADS=5 bundle exec good_job
$ GOOD_JOB_QUEUES="transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;*" GOOD_JOB_MAX_THREADS=5 bundle exec good_job
```

- Multiple processes; for example, on Heroku:
Expand Down
4 changes: 2 additions & 2 deletions lib/good_job/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class CLI < Thor
desc: "Maximum number of threads to use for working jobs (default: ActiveRecord::Base.connection_pool.size)"
method_option :queues,
type: :string,
banner: "queue1,queue2(;queue3,queue4:5)",
desc: "Queues to work from. Separate multiple queues with commas; separate isolated execution pools with semicolons and threads with colons (default: *)"
banner: "queue1,queue2(;queue3,queue4:5;-queue1,queue2)",
desc: "Queues to work from. Separate multiple queues with commas; exclude queues with a leading minus; separate isolated execution pools with semicolons and threads with colons (default: *)"
method_option :poll_interval,
type: :numeric,
desc: "Interval between polls for available jobs in seconds (default: 1)"
Expand Down
17 changes: 15 additions & 2 deletions lib/good_job/job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,21 @@ class Job < ActiveRecord::Base
scope :priority_ordered, -> { order('priority DESC NULLS LAST') }
scope :finished, ->(timestamp = nil) { timestamp ? where(arel_table['finished_at'].lteq(timestamp)) : where.not(finished_at: nil) }
scope :queue_string, (lambda do |string|
queue_names_without_all = (string.presence || '*').split(',').map(&:strip).reject { |q| q == '*' }
where(queue_name: queue_names_without_all) unless queue_names_without_all.size.zero?
string = string.presence || '*'

if string.first == '-'
exclude_queues = true
string = string[1..-1]
end

queue_names_without_all = string.split(',').map(&:strip).reject { |q| q == '*' }
return if queue_names_without_all.size.zero?

if exclude_queues
where.not(queue_name: queue_names_without_all).or where(queue_name: nil)
else
where(queue_name: queue_names_without_all)
end
end)

def self.perform_with_advisory_lock
Expand Down
17 changes: 17 additions & 0 deletions spec/lib/good_job/job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,23 @@ def perform(result_value = nil, raise_error: false)
end
end

describe '.queue_string' do
it 'separates commas' do
query = described_class.queue_string('first,second')
expect(query.to_sql).to eq described_class.where(queue_name: %w[first second]).to_sql
end

it 'excludes queues commas' do
query = described_class.queue_string('-first,second')
expect(query.to_sql).to eq described_class.where.not(queue_name: %w[first second]).or(described_class.where(queue_name: nil)).to_sql
end

it 'accepts empty strings' do
query = described_class.queue_string('')
expect(query.to_sql).to eq described_class.all.to_sql
end
end

describe '#perform' do
let(:active_job) { ExampleJob.new("a string") }
let!(:good_job) { described_class.enqueue(active_job) }
Expand Down