Skip to content

Commit

Permalink
Active Record and Active Job name formatting (#1182)
Browse files Browse the repository at this point in the history
* Active Record and Active Job name formatting

Initial exploration of some formatting changes for consistency with
Rails Guides and official sources:
https://guides.rubyonrails.org/index.html

- Active Job over ActiveJob
- Active Record over ActiveRecord

This requires close inspection though because ActiveJob and ActiveRecord
can be valid parts of a class name or constant in the GoodJob source
code.

* Update "Active Job", "Active Record", and "Action Mailer" in Readme

---------

Co-authored-by: Ben Sheldon [he/him] <[email protected]>
  • Loading branch information
andyatkinson and bensheldon authored Dec 18, 2023
1 parent a181f81 commit 50199f9
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 36 deletions.
53 changes: 27 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
[![Test Status](https://github.com/bensheldon/good_job/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/bensheldon/good_job/actions/workflows/test.yml?query=branch%3Amain)
[![Ruby Toolbox](https://img.shields.io/badge/dynamic/json?color=blue&label=Ruby%20Toolbox&query=%24.projects%5B0%5D.score&url=https%3A%2F%2Fwww.ruby-toolbox.com%2Fapi%2Fprojects%2Fcompare%2Fgood_job&logo=data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZm9jdXNhYmxlPSJmYWxzZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJmbGFzayIgY2xhc3M9InN2Zy1pbmxpbmUtLWZhIGZhLWZsYXNrIGZhLXctMTQiIHJvbGU9ImltZyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNDQ4IDUxMiI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik00MzcuMiA0MDMuNUwzMjAgMjE1VjY0aDhjMTMuMyAwIDI0LTEwLjcgMjQtMjRWMjRjMC0xMy4zLTEwLjctMjQtMjQtMjRIMTIwYy0xMy4zIDAtMjQgMTAuNy0yNCAyNHYxNmMwIDEzLjMgMTAuNyAyNCAyNCAyNGg4djE1MUwxMC44IDQwMy41Qy0xOC41IDQ1MC42IDE1LjMgNTEyIDcwLjkgNTEyaDMwNi4yYzU1LjcgMCA4OS40LTYxLjUgNjAuMS0xMDguNXpNMTM3LjkgMzIwbDQ4LjItNzcuNmMzLjctNS4yIDUuOC0xMS42IDUuOC0xOC40VjY0aDY0djE2MGMwIDYuOSAyLjIgMTMuMiA1LjggMTguNGw0OC4yIDc3LjZoLTE3MnoiPjwvcGF0aD48L3N2Zz4=)](https://www.ruby-toolbox.com/projects/good_job)

GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails.
GoodJob is a multithreaded, Postgres-based, Active Job backend for Ruby on Rails.
GoodJob is a multithreaded, Postgres-based, Active Job backend for Ruby on Rails.

**Inspired by [Delayed::Job](https://github.com/collectiveidea/delayed_job) and [Que](https://github.com/que-rb/que), GoodJob is designed for maximum compatibility with Ruby on Rails, ActiveJob, and Postgres to be simple and performant for most workloads.**
**Inspired by [Delayed::Job](https://github.com/collectiveidea/delayed_job) and [Que](https://github.com/que-rb/que), GoodJob is designed for maximum compatibility with Ruby on Rails, Active Job, and Postgres to be simple and performant for most workloads.**

- **Designed for ActiveJob.** Complete support for [async, queues, delays, priorities, timeouts, and retries](https://edgeguides.rubyonrails.org/active_job_basics.html) with near-zero configuration.
- **Designed for Active Job.** Complete support for [async, queues, delays, priorities, timeouts, and retries](https://edgeguides.rubyonrails.org/active_job_basics.html) with near-zero configuration.
- **Built for Rails.** Fully adopts Ruby on Rails [threading and code execution guidelines](https://guides.rubyonrails.org/threading_and_code_execution.html) with [Concurrent::Ruby](https://github.com/ruby-concurrency/concurrent-ruby).
- **Backed by Postgres.** Relies upon Postgres integrity, session-level Advisory Locks to provide run-once safety and stay within the limits of `schema.rb`, and LISTEN/NOTIFY to reduce queuing latency.
- **For most workloads.** Targets full-stack teams, economy-minded solo developers, and applications that enqueue 1-million jobs/day and more.
Expand Down Expand Up @@ -55,15 +56,15 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
- [Exceptions, retries, and reliability](#exceptions-retries-and-reliability)
- [Exceptions](#exceptions)
- [Retries](#retries)
- [ActionMailer retries](#actionmailer-retries)
- [Action Mailer retries](#action-mailer-retries)
- [Interrupts](#interrupts)
- [Timeouts](#timeouts)
- [Optimize queues, threads, and processes](#optimize-queues-threads-and-processes)
- [Database connections](#database-connections)
- [Production setup](#production-setup)
- [Queue performance with Queue Select Limit](#queue-performance-with-queue-select-limit)
- [Execute jobs async / in-process](#execute-jobs-async--in-process)
- [Migrate to GoodJob from a different ActiveJob backend](#migrate-to-goodjob-from-a-different-activejob-backend)
- [Migrate to GoodJob from a different Active Job backend](#migrate-to-goodjob-from-a-different-active-job-backend)
- [Monitor and preserve worked jobs](#monitor-and-preserve-worked-jobs)
- [Write tests](#write-tests)
- [PgBouncer compatibility](#pgbouncer-compatibility)
Expand Down Expand Up @@ -103,7 +104,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
bin/rails db:migrate:animals
```

1. Configure the ActiveJob adapter:
1. Configure the Active Job adapter:

```ruby
# config/application.rb or config/environments/{RAILS_ENV}.rb
Expand All @@ -116,7 +117,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
YourJob.perform_later
```

GoodJob supports all ActiveJob features:
GoodJob supports all Active Job features:

```ruby
YourJob.set(queue: :some_queue, wait: 5.minutes, priority: 10).perform_later
Expand Down Expand Up @@ -222,7 +223,7 @@ and this command is not required to be used.

### Configuration options

ActiveJob configuration depends on where the code is placed:
Active Job configuration depends on where the code is placed:

- `config.active_job.queue_adapter = :good_job` within `config/application.rb` or `config/environments/*.rb`.
- `ActiveJob::Base.queue_adapter = :good_job` within an initializer (e.g. `config/initializers/active_job.rb`).
Expand Down Expand Up @@ -321,7 +322,7 @@ config.good_job.execution_mode = :external
Good Job’s general behavior can also be configured via attributes directly on the `GoodJob` module:
- **`GoodJob.configure_active_record { ... }`** Inject Active Record configuration into GoodJob's base model, for example, when using [multiple databases with ActiveRecord](https://guides.rubyonrails.org/active_record_multiple_databases.html) or when other custom configuration is necessary for the ActiveRecord model to connect to the Postgres database. Example:
- **`GoodJob.configure_active_record { ... }`** Inject Active Record configuration into GoodJob's base model, for example, when using [multiple databases with Active Record](https://guides.rubyonrails.org/active_record_multiple_databases.html) or when other custom configuration is necessary for the Active Record model to connect to the Postgres database. Example:
```ruby
# config/initializers/good_job.rb
Expand All @@ -331,7 +332,7 @@ Good Job’s general behavior can also be configured via attributes directly on
end
```
- **`GoodJob.active_record_parent_class`** (string) Alternatively, modify the ActiveRecord parent class inherited by GoodJob's Active Record model `GoodJob::Job` (defaults to `"ActiveRecord::Base"`). Configure this _The value must be a String to avoid premature initialization of ActiveRecord._
- **`GoodJob.active_record_parent_class`** (string) Alternatively, modify the Active Record parent class inherited by GoodJob's Active Record model `GoodJob::Job` (defaults to `"ActiveRecord::Base"`). Configure this _The value must be a String to avoid premature initialization of Active Record._
You’ll generally want to configure these in `config/initializers/good_job.rb`, like so:
Expand Down Expand Up @@ -465,7 +466,7 @@ Labels can be used to search jobs in the Dashboard. For example, to find all job
### Concurrency controls
GoodJob can extend ActiveJob to provide limits on concurrently running jobs, either at time of _enqueue_ or at _perform_. Limiting concurrency can help prevent duplicate, double or unnecessary jobs from being enqueued, or race conditions when performing, for example when interacting with 3rd-party APIs.
GoodJob can extend Active Job to provide limits on concurrently running jobs, either at time of _enqueue_ or at _perform_. Limiting concurrency can help prevent duplicate, double or unnecessary jobs from being enqueued, or race conditions when performing, for example when interacting with 3rd-party APIs.
```ruby
class MyJob < ApplicationJob
Expand Down Expand Up @@ -494,7 +495,7 @@ class MyJob < ApplicationJob
# A unique key to be globally locked against.
# Can be String or Lambda/Proc that is invoked in the context of the job.
# Note: Arguments passed to #perform_later can be accessed through ActiveJob's `arguments` method
# Note: Arguments passed to #perform_later can be accessed through Active Job's `arguments` method
# which is an array containing positional arguments and, optionally, a kwarg hash.
key: -> { "MyJob-#{arguments.first}-#{arguments.last[:version]}" } # MyJob.perform_later("Alice", version: 'v2') => "MyJob-Alice-v2"
)
Expand Down Expand Up @@ -574,11 +575,11 @@ active_jobs = GoodJob::Bulk.enqueue do
# If an exception is raised within this block, no jobs will be inserted.
end
# All ActiveJob instances are returned from GoodJob::Bulk.enqueue.
# All Active Job instances are returned from GoodJob::Bulk.enqueue.
# Jobs that have been successfully enqueued have a `provider_job_id` set.
active_jobs.all?(&:provider_job_id)
# Bulk enqueue ActiveJob instances directly without using `.perform_later`:
# Bulk enqueue Active Job instances directly without using `.perform_later`:
GoodJob::Bulk.enqueue(MyJob.new, AnotherJob.new)
```
Expand Down Expand Up @@ -816,7 +817,7 @@ Notable changes:
- Renames `:async_server` execution mode to `:async`; renames prior `:async` execution mode to `:async_all`.
- Sets default Development environment's execution mode to `:async` with disabled polling.
- Excludes performing jobs from `enqueue_limit`'s count in `GoodJob::ActiveJobExtensions::Concurrency`.
- Triggers `GoodJob.on_thread_error` for unhandled ActiveJob exceptions.
- Triggers `GoodJob.on_thread_error` for unhandled Active Job exceptions.
- Renames `GoodJob.reperform_jobs_on_standard_error` accessor to `GoodJob.retry_on_unhandled_error`.
- Renames `GoodJob::Adapter.shutdown(wait:)` argument to `GoodJob::Adapter.shutdown(timeout:)`.
- Changes Advisory Lock key format from `good_jobs[ROW_ID]` to `good_jobs-[ACTIVE_JOB_ID]`.
Expand All @@ -826,11 +827,11 @@ Notable changes:
### Exceptions, retries, and reliability
GoodJob guarantees that a completely-performed job will run once and only once. GoodJob fully supports ActiveJob's built-in functionality for error handling, retries and timeouts.
GoodJob guarantees that a completely-performed job will run once and only once. GoodJob fully supports Active Job's built-in functionality for error handling, retries and timeouts.
#### Exceptions
ActiveJob provides [tools for rescuing and retrying exceptions](https://guides.rubyonrails.org/active_job_basics.html#exceptions), including `retry_on`, `discard_on`, `rescue_from` that will rescue exceptions before they get to GoodJob.
Active Job provides [tools for rescuing and retrying exceptions](https://guides.rubyonrails.org/active_job_basics.html#exceptions), including `retry_on`, `discard_on`, `rescue_from` that will rescue exceptions before they get to GoodJob.
If errors do reach GoodJob, you can assign a callable to `GoodJob.on_thread_error` to be notified. For example, to log errors to an exception monitoring service like Sentry (or Bugsnag, Airbrake, Honeybadger, etc.):
Expand All @@ -841,9 +842,9 @@ GoodJob.on_thread_error = -> (exception) { Rails.error.report(exception) }
#### Retries
By default, GoodJob relies on ActiveJob's retry functionality.
By default, GoodJob relies on Active Job's retry functionality.
ActiveJob can be configured to retry an infinite number of times, with a polynomial backoff. Using ActiveJob's `retry_on` prevents exceptions from reaching GoodJob:
Active Job can be configured to retry an infinite number of times, with a polynomial backoff. Using Active Job's `retry_on` prevents exceptions from reaching GoodJob:
```ruby
class ApplicationJob < ActiveJob::Base
Expand Down Expand Up @@ -885,7 +886,7 @@ end
By default, jobs will not be retried unless `retry_on` is configured. This can be overridden by setting `GoodJob.retry_on_unhandled_error` to `true`; GoodJob will then retry the failing job immediately and infinitely, potentially causing high load.
#### ActionMailer retries
#### Action Mailer retries
Any configuration in `ApplicationJob` will have to be duplicated on `ActionMailer::MailDeliveryJob` because ActionMailer uses that custom class which inherits from `ActiveJob::Base`, rather than your application's `ApplicationJob`.
Expand All @@ -911,7 +912,7 @@ might also be configured to use (deprecated now) `ActionMailer::DeliveryJob`.
Jobs will be automatically retried if the process is interrupted while performing a job, for example as the result of a `SIGKILL` or power failure.
If you need more control over interrupt-caused retries, include the `GoodJob::ActiveJobExtensions::InterruptErrors` extension in your job class. When an interrupted job is retried, the extension will raise a `GoodJob::InterruptError` exception within the job, which allows you to use ActiveJob's `retry_on` and `discard_on` to control the behavior of the job.
If you need more control over interrupt-caused retries, include the `GoodJob::ActiveJobExtensions::InterruptErrors` extension in your job class. When an interrupted job is retried, the extension will raise a `GoodJob::InterruptError` exception within the job, which allows you to use Active Job's `retry_on` and `discard_on` to control the behavior of the job.
```ruby
class MyJob < ApplicationJob
Expand Down Expand Up @@ -1201,9 +1202,9 @@ Depending on your application configuration, you may need to take additional ste
If you are using cron-style jobs, you might also want to look at your Passenger configuration, especially at [`passenger_pool_idle_time`](https://www.phusionpassenger.com/library/config/nginx/reference/#passenger_pool_idle_time) and [`passenger_min_instances`](https://www.phusionpassenger.com/library/config/nginx/reference/#passenger_min_instances) to make sure there's always at least once process running that can execute cron-style scheduled jobs. See also [Passenger's optimization guide](https://www.phusionpassenger.com/library/config/nginx/optimization/#minimizing-process-spawning) for more information.
### Migrate to GoodJob from a different ActiveJob backend
### Migrate to GoodJob from a different Active Job backend
If your application is already using an ActiveJob backend, you will need to install GoodJob to enqueue and perform newly created jobs _and_ finish performing pre-existing jobs on the previous backend.
If your application is already using an Active Job backend, you will need to install GoodJob to enqueue and perform newly created jobs _and_ finish performing pre-existing jobs on the previous backend.
1. Enqueue newly created jobs on GoodJob either entirely by setting `ActiveJob::Base.queue_adapter = :good_job` or progressively via individual job classes:
Expand All @@ -1223,7 +1224,7 @@ If your application is already using an ActiveJob backend, you will need to inst
worker: bundle exec que ./config/environment.rb & bundle exec good_job & wait -n
```
1. Once you are confident that no unperformed jobs remain in the previous ActiveJob backend, code and configuration for that backend can be completely removed.
1. Once you are confident that no unperformed jobs remain in the previous Active Job backend, code and configuration for that backend can be completely removed.
### Monitor and preserve worked jobs
Expand Down Expand Up @@ -1296,7 +1297,7 @@ A workaround to this limitation is to make a direct database connection availabl
url: postgres://database_host/my_database
```
1. Create a new ActiveRecord base class that uses the direct database connection
1. Create a new Active Record base class that uses the direct database connection
```ruby
# app/models/application_direct_record.rb
Expand All @@ -1307,7 +1308,7 @@ A workaround to this limitation is to make a direct database connection availabl
end
```
1. Configure GoodJob to use the newly created ActiveRecord base class:
1. Configure GoodJob to use the newly created Active Record base class:
```ruby
# config/initializers/good_job.rb
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/good_job/jobs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def destroy
def redirect_on_error(exception)
alert = case exception
when GoodJob::Job::AdapterNotGoodJobError
"ActiveJob Queue Adapter must be GoodJob."
"Active Job Queue Adapter must be GoodJob."
when GoodJob::Job::ActionForStateMismatchError
"Job is not in an appropriate state for this action."
else
Expand Down
2 changes: 1 addition & 1 deletion app/models/good_job/base_execution.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module GoodJob
# ActiveRecord model to share behavior between {Job} and {Execution} models
# Active Record model to share behavior between {Job} and {Execution} models
# which both read out of the same table.
class BaseExecution < BaseRecord
include AdvisoryLockable
Expand Down
8 changes: 4 additions & 4 deletions app/models/good_job/execution.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module GoodJob
# ActiveRecord model that represents an +ActiveJob+ job.
# Active Record model that represents an +ActiveJob+ job.
class Execution < BaseExecution
# Raised if something attempts to execute a previously completed Execution again.
PreviouslyPerformedError = Class.new(StandardError)
Expand Down Expand Up @@ -150,9 +150,9 @@ def self.queue_parser(string)
# @return [ActiveRecord::Relation]
scope :schedule_ordered, -> { order(coalesce_scheduled_at_created_at.asc) }

# Get Jobs were completed before the given timestamp. If no timestamp is
# provided, get all jobs that have been completed. By default, GoodJob
# destroys jobs after they are completed and this will find no jobs.
# Get completed jobs before the given timestamp. If no timestamp is
# provided, get *all* completed jobs. By default, GoodJob
# destroys jobs after they're completed, meaning this returns no jobs.
# However, if you have changed {GoodJob.preserve_job_records}, this may
# find completed Jobs.
# @!method finished(timestamp = nil)
Expand Down
8 changes: 4 additions & 4 deletions app/models/good_job/job.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# frozen_string_literal: true

module GoodJob
# ActiveRecord model that represents an +ActiveJob+ job.
# Active Record model that represents an +ActiveJob+ job.
# There is not a table in the database whose discrete rows represents "Jobs".
# The +good_jobs+ table is a table of individual {GoodJob::Execution}s that share the same +active_job_id+.
# A single row from the +good_jobs+ table of executions is fetched to represent a Job.
#
class Job < BaseExecution
# Raised when an inappropriate action is applied to a Job based on its state.
ActionForStateMismatchError = Class.new(StandardError)
# Raised when an action requires GoodJob to be the ActiveJob Queue Adapter but GoodJob is not.
# Raised when GoodJob is not configured as the Active Job Queue Adapter
AdapterNotGoodJobError = Class.new(StandardError)
# Attached to a Job's Execution when the Job is discarded.
DiscardJobError = Class.new(StandardError)
Expand Down Expand Up @@ -60,7 +60,7 @@ def table_name=(_value)

scope :unfinished_undiscrete, -> { where(finished_at: nil, retried_good_job_id: nil, is_discrete: [nil, false]) }

# The job's ActiveJob UUID
# The job's Active Job UUID
# @return [String]
def id
active_job_id
Expand Down Expand Up @@ -101,7 +101,7 @@ def tail_execution
executions.first
end

# The number of times this job has been executed, according to ActiveJob's serialized state.
# The number of times this job has been executed, according to Active Job's serialized state.
# @return [Numeric]
def executions_count
aj_count = serialized_params.fetch('executions', 0)
Expand Down

0 comments on commit 50199f9

Please sign in to comment.