GoodJob: background jobs in the database you already run
Multithreaded, Postgres-based, Active Job backend for Ruby on Rails.
At a glance
- What is it?
- A Rails Active Job backend that keeps its queue in Postgres and executes work in threads inside your app or in a separate process. The design choice that matters is that the queue and the results share a transaction.
- Who is it for?
- GoodJob's argument is not that Redis queues are bad but that most Rails applications do not need a second datastore to run a queue. If the enqueue happens inside a transaction with the record it acts on, the job and the data commit together or not at all, and no Redis deployment is needed to make that true.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 15 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why put the queue in Postgres
The README's six bullet points make the case, and the third one is the technical core: GoodJob relies on Postgres integrity, session-level advisory locks for run-once safety and staying within the limits of `schema.rb`, and LISTEN/NOTIFY to reduce queuing latency.
Each of those three is a deliberate answer to a specific problem. Postgres integrity means the job record and the data it acts on can be committed in the same transaction, so a job never exists for a change that rolled back and a committed change never lacks its job. Advisory locks give run-once semantics without a separate lock service: a worker claims a job by taking a lock keyed to the row, and if the process dies the lock dies with the connection. Staying inside `schema.rb` matters practically, since it means the queue tables can be migrated with the same tool as the rest of the application rather than needing a separate schema dump.
LISTEN/NOTIFY is the latency story. Polling a table for new rows means sleeping between checks, and LISTEN/NOTIFY lets Postgres wake a waiting worker the moment a row is inserted. The cost, which the README does not spell out, is that the notification queue has a bounded size, and a burst larger than that degrades to polling. For one million jobs a day spread out that is not a problem. For a backfill that enqueues everything at once, it is.
The first line of the README is the summary: multithreaded, Postgres-based, Active Job backend for Ruby on Rails.
What the comparison table actually concedes
The README includes a comparison table of GoodJob against five other backends, hidden behind a details element. It is worth reading closely because the honest parts are in the same row as the flattering ones.
Against Solid Queue, GoodJob's claimed advantages are multithreading in the main process rather than a forked one, and LISTEN/NOTIFY rather than polling. Solid Queue's advantages in that row are support for databases other than Postgres, which GoodJob does not have. Against Que, GoodJob claims the edge on not requiring `structure.sql`, which is Que's known deployment friction. Against Delayed Job, GoodJob notes it is single-threaded, which is accurate, and polling for latency.
The Sidekiq rows are where the table is least comfortable, and the most informative. Sidekiq is multithreaded and has low latency, both marked as advantages for it, and its database is Redis rather than Postgres. The reliability column reads that Sidekiq loses jobs when it crashes, while Sidekiq Pro uses RPOPLPUSH. That is the real tradeoff: Redis-backed queues are fast and simple but a crash between popping and processing can drop work, and the commercial version fixes it with a different pop command. Postgres-backed queues get durability from a write-ahead log the application already depends on.
Whether that matters is a question about your failure tolerance. For a job that sends a receipt email, an at-least-once queue with possible duplicates is fine. For a payment, you want to know the durability story before you choose.
Threads in the web process, or a separate process
The flexibility bullet says it plainly: GoodJob is safely runnable within a single existing web process, or scaled via an independent CLI process across development, test and production environments. That is the deployment decision, and it is the one most likely to shape your experience.
Running in the web process means one fewer thing to deploy and a very natural local development experience, since jobs execute in the same process that enqueued them. It also means your job threads compete with request handling for the same resources, and the README is explicit that it follows Rails' threading and code execution guidelines using Concurrent::Ruby. The dedicated process reverses that: clean resource isolation and independent scaling, at the cost of another process to supervise and one more thing that must be running.
The deeper operational sections in the table of contents point at what this actually requires. There is a whole page on database connections, because a thread pool with N threads needs N connections, and running GoodJob inside a Rails app inside a connection pooler is a known source of exhaustion. There is a page on PgBouncer compatibility, a page on CLI HTTP health check probes so an orchestrator can tell whether the worker is alive rather than merely whether the process exists, and a page on graceful shutdown and SIGKILL that explains what happens to an in-flight job when the process dies.
That last set of pages is the best signal of maturity here. Newer job backends tend to document features; this one documents failure modes, which is what you need at three in the morning.
The features beyond the queue itself
Cron-style scheduled jobs, bulk enqueue, batches, and concurrency and throttling controls are all listed as included, and there is also a Web Dashboard with a public demo instance linked from the repository description.
The concurrency controls get their own section with a subsection explaining how they work, which is the right treatment because this is where a Postgres queue has to make real choices. With a Redis queue you pop a job and it is yours. With Postgres and advisory locks, a worker has to select candidates and then attempt to lock them, and the failure mode is workers contending for the same rows. The throttling controls are the answer to that: rather than many workers fighting over one popular job class, you bound how many run at once.
Batches are the feature to look at if you are migrating from something else, because they are the group-operation primitive that Active Job does not provide. The v4.19.2 release added a way to fetch a batch by its IDs through GlobalID, which is a small change that suggests batch operations are actively used and are being made more composable with the rest of Rails.
There is also a section titled Doing your best job with GoodJob, which contains sizing advice framed as mice and elephants: small frequent jobs and large infrequent ones want different queue configurations, because one queue with one concurrency setting cannot serve both. Isolating by total latency is the idea, putting the jobs that must respond quickly in their own queue with their own limits.
Reading the release notes as a maintenance signal
The three most recent releases are v4.19.3 on 2026-09-21, v4.19.2 on 2026-07-20 and v4.19.1 on 2026-06-25. The numbering tells you the project is past the interesting early phase and into a stable minor cadence.
What is in the changes is the useful part. v4.19.3 added an index for discarded jobs keyed on job class, which is a pure performance fix with no behavior change, added the ability to pause continuation jobs, added a configuration validity check for cron setup, added time range controls to the dashboard's time series charts, and deprecated a fallback path for when `probe_handler` cannot be used. It also added an Azerbaijani locale.
That Azerbaijani locale in the same release as a cron validation method is the clearest evidence of what kind of project this is. It is not a company product with a support SLA. It is a widely used open-source gem maintained by someone who cares about both the queue internals and the i18n, with a contributor pipeline that brings in first-time contributors and lists them by name.
v4.19.2 is similar: preserve `created_at` when a job is retried, resolve the connection pool at call time in a shim, and remove a configuration option called `inline_execution_respects_schedule` rather than leaving it deprecated forever. v4.19.1 is two fixes, emitting ActiveSupport::Notifications when an enqueue is aborted by concurrency control, and supporting a Rails edge version of Arel::Table.
The project is MIT licensed with roughly 3,000 stars and 131 open issues, and the default branch is `main` with a last recorded push on 2026-09-21.
What this repository is unusually good at telling you
The README is not a feature page. It is very long, and its table of contents is the best argument for the project: migration guides for v1 through v4, exceptions and retries, Action Mailer retries, timeouts, optimizing queues and threads and processes, database connections, production setup, queue performance with Queue Select Limit, executing jobs in process, migrating from a different Active Job backend, monitoring and preserving worked jobs, writing tests, PgBouncer compatibility, HTTP health check probes, and pausing jobs.
Several of those are things competitors do not document at all. Preserving worked jobs is the answer to the question every Postgres queue gets asked, which is whether the rows pile up forever. Pausing jobs is an operational feature you want during an incident and discover you lack only when you need it. Writing tests gets its own page, which is the practical question for anyone adopting a background job system, since a queue that runs outside the request cycle breaks the usual test patterns.
The repository tree matches that emphasis. Alongside the usual `app/`, `lib/`, `spec/` and `exe/` there is a `demo/` directory and an `examples/` directory, an `AGENTS.md` file, a `sorbet/` directory for type signatures, `gemfiles/` for testing against multiple dependency sets, `checksums/`, and a `.devcontainer/`. The `AGENTS.md` at the root is itself an example of the convention Twill and others write for, one that a large project maintains for contributors and coding agents alike.
If you are evaluating this, the migration page from other backends and the production setup page are the two that will save you the most time, and both are more detailed than most projects' quick starts.
Editorial conclusion
GoodJob's argument is not that Redis queues are bad but that most Rails applications do not need a second datastore to run a queue. If the enqueue happens inside a transaction with the record it acts on, the job and the data commit together or not at all, and no Redis deployment is needed to make that true. The costs are the ones that follow from using Postgres as a queue: you need a connection per thread, the dashboard and the queue share the same database, and LISTEN/NOTIFY has a hard queue size limit beyond which it degrades to polling. Release v4.19.3 landed on 2026-09-21 with the last recorded push the same day, and the changelog's mix of indexes, deprecations and locale additions suggests a project in its mature phase rather than one adding capabilities.
Frequently asked questions
Is GoodJob a Redis job queue like Sidekiq?
No, it stores its queue in Postgres rather than Redis, which is the whole design. That is what lets a job be enqueued in the same transaction as the data change it acts on, and it is why the queue tables live in your schema and why connection management is a documented topic.
Do I need a separate process to run GoodJob workers?
No. It is designed to run safely inside an existing web process, or scaled separately through its own CLI process across development, test and production. Running in the web process is simpler; a separate process isolates job threads from request handling and is the better fit once throughput matters.
How does GoodJob guarantee a job runs only once?
Through session-level Postgres advisory locks. A worker claims a job by taking a lock keyed to the row, so two workers cannot process it simultaneously, and if a worker dies the lock is released when its connection drops. That is also why the README stresses staying within the limits of schema.rb.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bensheldon-good-job)