Migrate from DelayedJob to GoodJob
2023-10-13 4 min
Recently, I decided to replace DelayedJob by GoodJob. There was no major performance reason but the fact of DelayedJob’s low maintenance and GoodJob’s comprehensive documentation convinced me to give it a try.
After some days of testing I decided to switch to Goodjob. Here are the key factors that convinced me:
- Minimum setup hassle: no initializers required at least in my case;
- The Dashboard: integrated by default and more comprehensive;
- Convenient Development: running GoodJob in the same thread than the app server in development mode;
- Inline job execution: immediate job execution for tests. DelayedJob also supports inline execution for testing, but again, it requires some setup and it is quite hidden in the doc.
- Multithreaded: DelayedJob did not support concurrency but with GoodJob the app will be ready to scale.
Anyway, the focus of the article it’s not drawing comparisons between the two libraries (everyone can have their own reasons) but rather on providing some guidance on the migration from DelayedJob to GoodJob and, more broadly, transitioning between two background workers.
💡 This migration guide is based on the use of the ActiveJob interface, which actually makes the migration much easier.
The following guide has been elaborated as a checklist in two steps:
- Both workers run side by side
- Decommissioning DelayedJob
Please bear in mind that this isn’t an exhaustive manual but rather a set of general guidelines. You’ll need to customize these recommendations to fit the specific nuances of your application.
Both workers run side by side
In this first step the goal is to setup GoodJob without removing DelayedJob. The key point here is to run both background workers side by side: GoodJob will handle new jobs, while DelayedJob will continue processing pending jobs until none remain.
- Follow the instructions to install GoodJob.
- Change the worker adapter in your
config/application.rb
file.# config/application.rb config.active_job.queue_adapter = :good_job
- Run the tests. Most likely new mocks/stubs will need to be added, if like me you had not configured DelayedJob to run the jobs “inline”.
- Add the GoodJob dashboard in your
config/routes.rb
.Rails.application.routes.draw do # http://localhost:3000/jobs mount Delayed::Web::Engine, at: "/jobs" # http://localhost:3000/good_job mount GoodJob::Engine => 'good_job' end
- If everything is OK locally, deploy in staging and check that everything is still working.
- Make sure both Both workers works side by side.
# Procfile web: bundle exec rails start worker: bundle exec rails jobs:work new_worker: bundle exec good_job start
- Deploy in production when you think is ready.
Decommissioning DelayedJob
Once there are no pending jobs in DelayedJob, it’s time to proceed to take it out as follows.
- Uninstall the gem
delayed-job-active-record
. If you were using also the DelayedJob dashboard, uninstall the delayed-web gem, too. - Create a migration that removes the DelayedJob tables + indexes.
class DropDelayedJobs < ActiveRecord::Migration[7.1] # Yes, I just recently migrated to Rails 7.1 😊 def change remove_index :delayed_jobs, name: "delayed_jobs_priority" drop_table :delayed_jobs end end
- Remove the initializers. I had two initializers:
rm config/initializers/delayed_web.rb config/initializers/delayed_job.rb
- Remove the DelayedJob dashboard in
config/routes.rb
Rails.application.routes.draw do # http://localhost:3000/good_job mount GoodJob::Engine => 'good_job' end
- Remove the worker start from your Procfile or similar.
# Procfile web: bundle exec rails start new_worker: bundle exec good_job start
- As always, test in staging. Nothing should be broken but never take it for granted.
- When you’re sure is all right, deploy in production.
Conclusion
And so far, the steps to follow to migrate from DelayedJob to GoodJob. It is not a complicated procedure because in this case both use the same storage database (Postgres) but a worker cannot be removed by simply unplugging it. It requires a series of steps to ensure that no pending work is lost during the switchover.
Another step I haven’t mentioned concerns the fine-tuning of the new worker engine. Perhaps instead of replacing DelayedJob with GoodJob in the first step, you’d rather keep working with DelayedJob and test GoodJob in production before taking it for granted. Just don’t replace config.active_job.queue_adapter
in the first step. Leave it for later.
And that’s it. Now you have a better idea of how to migrate a worker engine. The steps are more or less similar even if you change the storage system (e.g. Redis).