-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #358 from turingschool/316-background-workers
Background Workers Lesson
- Loading branch information
Showing
2 changed files
with
262 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
# 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](https://github.com/turingschool-examples/daily-mood-mailer-7). | ||
|
||
```bash | ||
$ git clone [email protected]: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: | ||
|
||
```bash | ||
$ 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`: | ||
|
||
```bash | ||
$ 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: | ||
|
||
```ruby | ||
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: | ||
|
||
```ruby | ||
bundle exec sidekiq | ||
``` | ||
|
||
You should get the fancy Sidekiq ASCII logo in your terminal: | ||
|
||
```bash | ||
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. | ||
```bash | ||
$ touch config/initializers/sidekiq.rb | ||
``` | ||
*config/initializers/sidekiq.rb* | ||
```ruby | ||
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* | ||
```ruby | ||
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: | ||
```bash | ||
$ rails generate sidekiq:job mood_sender | ||
``` | ||
This will generate your job class within a directory called `sidekiq`: | ||
*app/sidekiq/mood_sender_job.rb* | ||
```ruby | ||
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* | ||
```ruby | ||
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* | ||
```ruby | ||
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* | ||
```ruby | ||
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](https://github.com/turingschool-examples/daily-mood-mailer-7/tree/background-jobs-complete). |