Background Processing With Resque
Mike Cochran
@vongrippen
http://www.rvl.io/vongrippen/resque
A Quick Note though
This is a presentation on Resque 1.x
2.x is still in development even
though it is the "master" branch
Quick Info on Resque
Built by github
Github built it to solve their
background processing problems
after using various existing tools
https://github.com/blog/542-introducing-resque
Simple, Easy to Use
Defining a worker is easy, it just needs two things:
- A queue defined
- A class method: perform
gem 'resque', require: 'resque/server'
class MyAwesomeWorker
@queue= :bunny_rabbit
def self.perform(bunny_name)
sleep 30
puts "#{bunny_name} is an awesome bunny!"
end
end
env QUEUE='*' rake resque:work
Bundled with a Web UI
(Built in Sinatra)
the Problem
Simulating an investment portfolio
(Average Case)
- Invest for 30 years
- Rebalance investments after each year
- Simulate 205 times, incrementing the starting quarter
- Start in Dec 1932, invest for 30 years
- Start in Mar 1933, invest for 30 years
- Start in Jun 1933, invest for 30 years
- ... etc
- Save each simulation (called a "period")
- Collect the summary of each as a "Case Result"
Without Resque
Controller
def create
client_case.input = filtered_params
client_case.calculate
# 3 minutes later...
client.save
# Render HTML
end
The result?
Timeout Error
Here's the Slow down
Model
def calculate
periods = 0..(periods_to_run-1)
summaries = [ ]
periods.each do |period|
# Do a bunch of fun math
years_in_period = calculate_period(input, period)
self.save_period(period, years_in_period)
summaries << years_in_period.last
end
self.summary = summarize(summaries)
end
Let's Add Resque
With Resque
Controller
def create
client_case.input = filtered_params
client_case.save
Resque.enqueue(CaseWorker, client_case.id)
# Render HTML
end
CaseWorker
class CaseWorker
@queue = :cases
def self.perform(case_id)
client_case = Case.find(case_id)
client_case.calculate
client_case.save
end
end
The result?
No Timeout!
(But it still takes 3 minutes to calculate)
Here's the Slow down (Still)
Model
def calculate
periods = 0..(periods_to_run-1)
summaries = [ ]
periods.each do |period|
# Do a bunch of fun math
years_in_period = calculate_period(input, period)
self.save_period(period, years_in_period)
summaries << years_in_period.last
end
self.summary = summarize(summaries)
end
Let's Add (More) Resque
With (More) Resque
Controller
def create
client_case.input = filtered_params
client_case.save
(0..client_case.periods_to_run-1).each do |period|
Resque.enqueue(PeriodWorker, case_id, period)
end
# Render HTML
end
CaseWorker
class CaseWorker
@queue = :cases
def self.perform(case_id)
client_case = Case.find(case_id)
client_case.summary = summarize(client_case.summaries)
client_case.save
end
end
With (More) Resque
PeriodWorker
class PeriodWorker
@queue = :periods
def self.perform(case_id, period)
client_case = Case.find(case_id)
years_in_period = calculate_period(input, period)
client_case.save_period(period, years_in_period)
client_case.summaries << years_in_period.last
client_case.save
if client_case.periods == client_case.periods_in_case
Resque.enqueue(CaseWorker, case_id)
end
end
end
The result?
No Timeout!
Plus, I can now throw more machines at it.
With 30 "workers" running, 25 seconds.
What did we just do exactly?
- We separated the calculation from our web app itself
- We broke down our code into smaller bits
- We ensured that our code could be run in parallel on multiple machines
Run as many
Workers
as you want
Q & A
Resources
Project:
https://github.com/resque/resque
Screencast:
http://railscasts.com/episodes/271-resque
Sidekiq
sidekiq.org
- Uses threads instead of forks
- Easily extensible
- Has a lot of extra functionality out of the box
- Only loads a single copy of Rails in memory (if using Rails)
- Smaller footprint
CAVEATS
- You code must be thread-safe
- Any gem you use must be thread-safe
- More processor intensive
resque
By vongrippen
resque
- 1,901