Intro to Background Workers
Intro to Background Workers
Learning Goals
- Describe a background job and when we would want to use one
- Be able to implement a basic background job with Sidekiq
Intro
When building websites, it’s important to keep response times down. Long-running requests will tie up server resources, degrade user perception of your site, and make it hard to manage failures.
There’s a solution to this: return a successful response, and then schedule some computation to happen later, outside the original request/response cycle.
We do this with background job. A background job allows you to run process on a separate dedicated thread. We can move tasks that don’t have to happen in real time to their own process so that the app can move on, rather than causing a backlog of requests which slows everything down.
You may also hear ‘background jobs’ referred to as ‘background workers.’ These terms are interchangeable, but ‘worker’ is more common with older versions of Sidekiq.
Do you need a job queue?
How do you identify areas of the application that can benefit from a background job? Some common areas for asynchronous work:
- Data Processing - e.g. generating thumbnails or resizing images
- 3rd Party APIs - interacting with a service outside of your site
- Maintenance - expiring old sessions, sweeping caches
- Email/Text Services - a request that causes an email or SMS to be sent
- Scheduling a task - e.g. daily reminders, clearing data after a time period
Applications with good Object Oriented design make it easy to queue background jobs, while poor OO makes it hard to extract jobs since responsibilities tend to overlap.
App Setup
Let’s look at an example of making a background task using Sidekiq. We’ll use the main
branch of the “Daily Mood” application.
$ git clone git@github.com:turingschool-examples/daily-mood-mailer.git
$ cd daily-mood-mailer
$ bundle
$ rails db:{create,migrate}
$ rails s
This application’s homepage has a form that takes an email and a string to generate an email. Start the server rails s and you should be able to view the app at http://localhost:3000
which has form inputs for an email address and a mood. When you submit that form you should feel the pain of a slow page load.
Why?
Check out our UserNotifierMailer
mailer. A 5 second delay has been hard-coded in to simulate a real-life delay.
Notice that this process takes a very long time. What we have here is a perfect candidate for a background process:
- Operation is slow
- User’s interaction with the process is already asynchronous (submit the form then go check their email)
- Operation is well-encapsulated behind the UserNotifier interface
- Operation requires relatively little data as inputs (email address and mood).
Sidekiq and Resque are the 2 most popular queuing libraries for Ruby. For this application, we’ll use Sidekiq.
Dependency — Redis
Sidekiq uses Redis as a memory store for enqueued jobs. Make sure you have Redis installed and running:
$ brew update && brew install redis
Then, run redis-server
.
This command starts the Redis server on port 6379. Once the Redis server is running it is ready to queue jobs that still need to be done.
You can check if your Redis process is running by executing the command redis-cli
:
$ redis-cli
127.0.0.1:6379>
The need for running our Redis server now is similar to how Postgres must be running in order for ActiveRecord to interact with our database.
Sidekiq Setup
Sidekiq is the tool we will use to manage our background jobs.
We can add the sidekiq gem to our Gemfile:
gem "sidekiq"
Note: this gem should live outside of the :development, :test
group.
bundle the app and you should now be able to run a sidekiq process by executing the command:
bundle exec sidekiq
You should get the fancy Sidekiq ASCII logo in your terminal:
s
ss
sss sss ss
s sss s ssss sss ____ _ _ _ _
s sssss ssss / ___|(_) __| | ___| | _(_) __ _
s sss \___ \| |/ _` |/ _ \ |/ / |/ _` |
s sssss s ___) | | (_| | __/ <| | (_| |
ss s s |____/|_|\__,_|\___|_|\_\_|\__, |
s s s |_|
s s
sss
sss
(It should look like a person doing a karate kick in your terminal, not whatever thing you see above, formatting is hard.)
Next we need to add a Sidekiq initializer.
$ touch config/initializers/sidekiq.rb
config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
config.redis = { url: 'redis://localhost:6379/0' }
end
Sidekiq.configure_client do |config|
config.redis = { url: 'redis://localhost:6379/0' }
end
If your server is running, you must restart it after adding an initializer for it to take effect.
We do not need to do this step for the Daily Mood app, however, if you are running an API only application (like your consultancy backend apps) you must enable some sessions middleware that is excluded in API only apps by default:
config/application.rb
config.session_store :cookie_store, key: '_interslice_session'
config.middleware.use ActionDispatch::Cookies
config.middleware.use config.session_store, config.session_options
Creating the Job
Now we have Sidekiq running with our application, but so far it doesn’t do anything for us. Let’s create a job to handle our email.
In the terminal:
$ rails generate sidekiq:job mood_sender
This will generate your job class within a directory called sidekiq
:
app/sidekiq/mood_sender_job.rb
class MoodSenderJob
include Sidekiq::Job
def perform(*args)
# Do something
end
end
It is Sidekiq convention to use the suffix of Job
for your job classes.**
Defining Sidekiq Job Operations
Within a Sidekiq job, the instance method #perform
is what gets called whenever a job appears for our job to do. Other methods can live within the job class, but #perform
is what will be invoked when running the job.
Let’s think about what needs to go in here and about what inputs are required for the job:
- Needs to take in the email address and ‘mood’ (since these are the parameters needed to send the email)
- Needs to send an email using the UserNotifier
Given these constraints, it might look something like:
app/sidekiq/mood_sender_job.rb
class MoodSenderJob
include Sidekiq::Job
def perform(email, mood)
UserNotifierMailer.send_mood_email(email, mood).deliver_now
end
end
Queueing Jobs — Sidekiq::Job.perform_async
The Sidekiq job defines what actual work will be done whenever our background process is invoked. Now we just need to actually invoke it.
With Sidekiq, we dispatch a job for a job to do later by calling .perform_async
on our job and providing it whatever arguments are needed for the job.
Under the hood, the .perform_async
method writes data into Redis indicating the type of job which needs to be done and the data associated with it. The queues are monitored in a separate process so that whenever new jobs appear, they’ll be started!
Also commonly used is .perform_at
which can be reviewed in the Sidekiq docs. This method allows you to specify the date/time a job should execute.
Let’s see what it looks like to actually queue the job. Recall that we were previously sending the email directly from our MailersController
. Let’s replace the line that was sending the email.
app/controllers/mailers_controller.rb
class MailersController < ApplicationController
def create
MoodSenderJob.perform_async(params[:mailers][:email], params[:mailers][:mood])
flash[:message] = "You did it! Email sent to #{params[:mailers][:email]}"
redirect_to "/sent"
end
def sent
end
end
Remember – the arguments passed to the .perform_async
method here will eventually be handed to your job’s #perform
method, so make sure they match up.
Let’s try this out now - hopefully hat painful delay is no more!
Sidekiq Dashboard
Sidekiq provides a dashboard for us to monitor our local job queues.
In our routes file, we’ll need to mount the Sidekiq dashboard.
config/routes.rb
require 'sidekiq/web'
Rails.application.routes.draw do
root "home#show"
get "/sent", to: "mailers#sent"
resources :mailers, only: [:create]
mount Sidekiq::Web => '/sidekiq'
end
Now you can navigate to http://localhost:3000/sidekiq/
. This dashboard is very useful for testing out jobs and receiving confirmation that everything is queued according to plan.
Note: if you are getting errors when trying to visit the Sidekiq dashboard, try clearing your browser cookies.
Checks for Understanding
- What is a background job?
- Why would you use a background job?
- Describe Sidekiq and Redis, and draw a diagram of how they interact with Rails.
Further Practice
• Create your own Job class which creates an instance of a model asynchronously.
Completed code for this lesson can be found on the background-jobs-complete
branch here.