Show:

Delayed Job – Example on How to Deal with Long Running Rails Tasks

May 3, 2018 Programming

When an application needs to deal with some long running tasks, we probably should move those tasks to run asynchronously as background jobs, more precisely to use Rails Delayed Job. Let’s examine what we need to do in order to enqueue and execute some tasks in the background.

Set up Delayed Job

With Rails Active Job we can easily define our jobs and execute them on various queuing backends. Delayed Job is one of those backends, that perfectly match out needs.

Let’s assume that we already have a Rails application with one model named Team. For start, we will need to add delayed_job_active_record gem to app’s Gemfile,

gem 'delayed_job_active_record'

and run bundle install to install it.

Then we will run the following commands, to create delayed_jobs database table and delayed_job file in the bin directory

rails generate delayed_job:active_record
rails db:migrate

Next we will set delayed_job as ActiveJob queue adapter,

# config/application.rb

config.active_job.queue_adapter = :delayed_job

and set default delayed job configuration.

# config/initializers/delayed_job_config.rb

Delayed::Worker.destroy_failed_jobs = false
Delayed::Worker.sleep_delay = 60
Delayed::Worker.max_attempts = 3
Delayed::Worker.max_run_time = 5.minutes
Delayed::Worker.read_ahead = 10
Delayed::Worker.default_queue_name = 'default'
Delayed::Worker.delay_jobs = !Rails.env.test?
Delayed::Worker.raise_signal_exceptions = :term
Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log'))

In order to menage background processes we will add `gem to our Gemfile. That makes us completely ready to create and test out our first background job. Create the Job
[ruby]
rails generate job teams_statistic

This will create teams_statistic_job.rb file under the app/jobs directory, with the following content:

class TeamsStatisticJob < ApplicationJob
queue_as :default

def perform(*args)
# Do something later
end
end

Let’s suppose that, within Team class, we have one instance method for long and complex calculations, named calculate_statistic.

class Team < ApplicationRecord
...

def calculate_statistic
# some complex calculation

puts "I am calculating #{self.name} statistic!"
end
end

Now we can easily call that method from the job’s perform method, passing the team as an argument.

def perform(team)
team.calculate_statistic
end

Let’s enqueue a job through Rails console.

red_star = Team.find_by_name("Red Star")
TeamsStatisticJob.perform_later(red_star)

Because Delayed Job is database based queue system, previous command will insert one row in the delayed_jobs table.

Also through logs we can see that we just enqueued a job to be performed as soon as the queuing system is free.

Enqueued TeamsStatisticJob (Job ID: new-job-id) to DelayedJob(default) with arguments: GlobalID:...

Start background worker

We can start background workers with this rake task bundle exec rake jobs:work or by running bin/deleyed_job script (by the way, that’s the reason why we added daemons gem).

# commands that runs bin/deleyed_job script

RAILS_ENV=production bin/delayed_job start
RAILS_ENV=production bin/delayed_job stop

Let’s open new terminal and run the rake task to actually perform the job.

As our logs are showing, long running task is now successfully completed.

Starting job worker
Job ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper (id=12) (queue=default)
RUNNING

I am calculating Red Star statistic! # calculate_statistic method output

Job ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper (id=12) (queue=default)
COMPLETED after 0.0771

And that is it.

However, there are many useful features (priorities, named queues, …) in the Delayed Job documentation, that I didn’t cover in this post. I encourage everyone to check those out…