- Notifications
You must be signed in to change notification settings - Fork1.7k
Resque is a Redis-backed Ruby library for creating background jobs, placing them on multiple queues, and processing them later.
License
resque/resque
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Resque (pronounced like "rescue") is a Redis-backed library for creatingbackground jobs, placing those jobs on multiple queues, and processingthem later.
For the backstory, philosophy, and history of Resque's beginnings, please seethe blog post (2009).
Background jobs can be any Ruby class or module that responds toperform
. Your existing classes can easily be converted to backgroundjobs or you can create new classes specifically to do work. Or, youcan do both.
Resque is heavily inspired by DelayedJob (which rocks) and comprisesthree parts:
- A Ruby library for creating, querying, and processing jobs
- A Rake task for starting a worker which processes jobs
- A Sinatra app for monitoring queues, jobs, and workers.
Resque workers can be given multiple queues (a "queue list"),distributed between multiple machines,run anywhere with network access to the Redis server,support priorities, are resilient to memory bloat / "leaks,"tell you what they're doing, and expect failure.
Resque queues are persistent; support constant time, atomic push andpop (thanks to Redis); provide visibility into their contents; andstore jobs as simple JSON packages.
The Resque frontend tells you what workers are doing, what workers arenot doing, what queues you're using, what's in those queues, providesgeneral usage stats, and helps you track failures.
Resque now supports Ruby 2.3.0 and above.We will also only be supporting Redis 3.0 and above going forward.
Would you like to be involved in Resque? Do you have thoughts about whatResque should be and do going forward? There's currently anopen discussion hereon just that topic, so please feel free to join in. We'd love to hear your thoughtsand/or have people volunteer to be a part of the project!
Resque jobs are Ruby classes (or modules) which respond to theperform
method. Here's an example:
classArchive@queue=:file_servedefself.perform(repo_id,branch='master')repo=Repository.find(repo_id)repo.create_archive(branch)endend
The@queue
class instance variable determines which queueArchive
jobs will be placed in. Queues are arbitrary and created on the fly -you can name them whatever you want and have as many as you want.
To place anArchive
job on thefile_serve
queue, we might add thisto our application's pre-existingRepository
class:
classRepositorydefasync_create_archive(branch)Resque.enqueue(Archive,self.id,branch)endend
Now when we callrepo.async_create_archive('masterbrew')
in ourapplication, a job will be created and placed on thefile_serve
queue.
Later, a worker will run something like this code to process the job:
klass,args=Resque.reserve(:file_serve)klass.perform(*args)ifklass.respond_to?:perform
Which translates to:
Archive.perform(44,'masterbrew')
Let's start a worker to runfile_serve
jobs:
$ cd app_root$ QUEUE=file_serve rake resque:work
This starts one Resque worker and tells it to work off thefile_serve
queue. As soon as it's ready it'll try to run theResque.reserve
code snippet above and process jobs until it can'tfind any more, at which point it will sleep for a small period andrepeatedly poll the queue for more jobs.
Add the gem to your Gemfile:
gem 'resque'
Next, install it with Bundler:
$ bundle
In your Rakefile, or some other file inlib/tasks
(ex:lib/tasks/resque.rake
), load the resque rake tasks:
require'resque'require'resque/tasks'require'your/app'# Include this line if you want your workers to have access to your application
If you're using Rails 7.1.x, userack-session version 1.0.2. This is because resque-web's dependency,Sinatra, isn't compatible with rack 3.0, which isrequired by rack-session 2.0.0.
gem 'rails', '~> 7.1' gem 'rack-session', '~> 1.0', '>= 1.0.2'
To make resque specific changes, you can override theresque:setup
job inlib/tasks
(ex:lib/tasks/resque.rake
). GitHub's setup task looks like this:
task"resque:setup"=>:environmentdoGrit::Git.git_timeout=10.minutesend
We don't want thegit_timeout
as high as 10 minutes in our web app,but in the Resque workers it's fine.
Resque workers are rake tasks that run forever. They basically do this:
startloopdoifjob=reservejob.processelsesleep5# Polling frequency = 5endendshutdown
Starting a worker is simple:
$ QUEUE=* rake resque:work
Or, you can start multiple workers:
$ COUNT=2 QUEUE=* rake resque:workers
This will spawn two Resque workers, each in its own process. Hittingctrl-c should be sufficient to stop them all.
Resque doesn't support numeric priorities but instead uses the orderof queues you give it. We call this list of queues the "queue list."
Let's say we add awarm_cache
queue in addition to ourfile_serve
queue. We'd now start a worker like so:
$ QUEUES=file_serve,warm_cache rake resque:work
When the worker looks for new jobs, it will first checkfile_serve
. If it finds a job, it'll process it then checkfile_serve
again. It will keep checkingfile_serve
until no morejobs are available. At that point, it will checkwarm_cache
. If itfinds a job it'll process it then checkfile_serve
(repeating thewhole process).
In this way you can prioritize certain queues. At GitHub we start ourworkers with something like this:
$ QUEUES=critical,archive,high,low rake resque:work
Notice thearchive
queue - it is specialized and in our futurearchitecture will only be run from a single machine.
At that point we'll start workers on our generalized backgroundmachines with this command:
$ QUEUES=critical,high,low rake resque:work
And workers on our specialized archive machine with this command:
$ QUEUE=archive rake resque:work
If you want your workers to work off of every queue, including newqueues created on the fly, you can use a splat:
$ QUEUE=* rake resque:work
Queues will be processed in alphabetical order.
Or, prioritize some queues above*
:
# QUEUE=critical,* rake resque:work
If you want your workers to work off of all queues except for some,you can use negation:
$ QUEUE=*,!low rake resque:work
Negated globs also work. The following will instruct workers to workoff of all queues except those beginning withfile_
:
$ QUEUE=*,!file_* rake resque:work
Note that the order in which negated queues are specified does notmatter, soQUEUE=*,!file_*
andQUEUE=!file_*,*
will have the sameeffect.
There are scenarios where it's helpful to record the PID of a resqueworker process. Use the PIDFILE option for easy access to the PID:
$ PIDFILE=./resque.pid QUEUE=file_serve rake resque:work
There are scenarios where it's helpful forthe resque worker to run itself in the background (usually in combination withPIDFILE). Use the BACKGROUND option so that rake will return as soon as theworker is started.
$ PIDFILE=./resque.pid BACKGROUND=yes QUEUE=file_serve rake resque:work
You can pass an INTERVAL option which is a float representing the polling frequency.The default is 5 seconds, but for a semi-active app you may want to use a smaller value.
$ INTERVAL=0.1 QUEUE=file_serve rake resque:work
When INTERVAL is set to 0 it will run until the queue is empty and thenshutdown the worker, instead of waiting for new jobs.
Resque comes with a Sinatra-based front end for seeing what's up withyour queue.
If you've installed Resque as a gem running the front end standalone is easy:
$ resque-web
It's a thin layer aroundrackup
so it's configurable as well:
$ resque-web -p 8282
If you have a Resque config file you want evaluated just pass it tothe script as the final argument:
$ resque-web -p 8282 rails_root/config/initializers/resque.rb
You can also set the namespace directly usingresque-web
:
$ resque-web -p 8282 -N myapp
or set the Redis connection string if you need to do something like select a different database:
$ resque-web -p 8282 -r localhost:6379:2
Using Passenger? Resque ships with aconfig.ru
you can use. SeePhusion's guide:
Apache:https://www.phusionpassenger.com/library/deploy/apache/deploy/ruby/Nginx:https://www.phusionpassenger.com/library/deploy/nginx/deploy/ruby/
If you want to load Resque on a subpath, possibly alongside otherapps, it's easy to do with Rack'sURLMap
:
require'resque/server'runRack::URLMap.new \"/"=>Your::App.new,"/resque"=>Resque::Server.new
Checkexamples/demo/config.ru
for a functional example (includingHTTP basic auth).
You can also mount Resque on a subpath in your existing Rails app by addingrequire 'resque/server'
to the top of your routes file or in an initializer then adding this toroutes.rb
:
mountResque::Server.new,:at=>"/resque"
What should you run in the background? Anything that takes any time atall. Slow INSERT statements, disk manipulating, data processing, etc.
At GitHub we use Resque to process the following types of jobs:
- Warming caches
- Counting disk usage
- Building tarballs
- Building Rubygems
- Firing off web hooks
- Creating events in the db and pre-caching them
- Building graphs
- Deleting users
- Updating our search index
As of writing we have about 35 different types of background jobs.
Keep in mind that you don't need a web app to use Resque - we justmention "foreground" and "background" because they make conceptualsense. You could easily be spidering sites and sticking data whichneeds to be crunched later into a queue.
Jobs are persisted to queues as JSON objects. Let's take ourArchive
example from above. We'll run the following code to create a job:
repo=Repository.find(44)repo.async_create_archive('masterbrew')
The following JSON will be stored in thefile_serve
queue:
{'class':'Archive','args':[44,'masterbrew']}
Because of this your jobs must only accept arguments that can be JSON encoded.
So instead of doing this:
Resque.enqueue(Archive,self,branch)
do this:
Resque.enqueue(Archive,self.id,branch)
This is why our above example (and all the examples inexamples/
)uses object IDs instead of passing around the objects.
While this is less convenient than just sticking a marshaled objectin the database, it gives you a slight advantage: your jobs will berun against the most recent version of an object because they need topull from the DB or cache.
If your jobs were run against marshaled objects, they couldpotentially be operating on a stale record with out-of-date information.
Want something like DelayedJob'ssend_later
or the ability to useinstance methods instead of just methods for jobs? See theexamples/
directory for goodies.
We plan to provide first classasync
support in a future release.
If a job raises an exception, it is logged and handed off to theResque::Failure
module. Failures are logged either locally in Redisor using some different backend. To see exceptions while developing,see details below under Logging.
For example, Resque ships with Airbrake support. To configure it, putthe following into an initialisation file or into your rake job:
# send errors which occur in background jobs to redis and airbrakerequire'resque/failure/multiple'require'resque/failure/redis'require'resque/failure/airbrake'Resque::Failure::Multiple.classes=[Resque::Failure::Redis,Resque::Failure::Airbrake]Resque::Failure.backend=Resque::Failure::Multiple
Keep this in mind when writing your jobs: you may want to throwexceptions you would not normally throw in order to assist debugging.
If you are using ActiveJob here's how your job definition will look:
classArchiveJob <ApplicationJobqueue_as:file_servedefperform(repo_id,branch='master')repo=Repository.find(repo_id)repo.create_archive(branch)endend
classRepositorydefasync_create_archive(branch)ArchiveJob.perform_later(self.id,branch)endend
It is important to runArchiveJob.perform_later(self.id, branch)
rather thanResque.enqueue(Archive, self.id, branch)
.Otherwise Resque will process the job without actually doing anything.Even if you put an obviously buggy line like0/0
in theperform
method,the job will still succeed.
You may want to change the Redis host and port Resque connects to, orset various other options at startup.
Resque has aredis
setter which can be given a string or a Redisobject. This means if you're already using Redis in your app, Resquecan re-use the existing connection.
String:Resque.redis = 'localhost:6379'
Redis:Resque.redis = $redis
For our rails app we have aconfig/initializers/resque.rb
file wherewe loadconfig/resque.yml
by hand and set the Redis informationappropriately.
Here's ourconfig/resque.yml
:
development: localhost:6379test: localhost:6379staging: redis1.se.github.com:6379fi: localhost:6379production: <%= ENV['REDIS_URL'] %>
And our initializer:
rails_root=ENV['RAILS_ROOT'] ||File.dirname(__FILE__) +'/../..'rails_env=ENV['RAILS_ENV'] ||'development'config_file=rails_root +'/config/resque.yml'resque_config=YAML::load(ERB.new(IO.read(config_file)).result)Resque.redis=resque_config[rails_env]
Easy peasy! Why not just useRAILS_ROOT
andRAILS_ENV
? Becausethis way we can tell our Sinatra app about the config file:
$ RAILS_ENV=production resque-web rails_root/config/initializers/resque.rb
Now everyone is on the same page.
Also, you could disable jobs queueing by setting 'inline' attribute.For example, if you want to run all jobs in the same process for cucumber, try:
Resque.inline=ENV['RAILS_ENV'] =="cucumber"
Workers support basic logging to STDOUT.
You can control the logging threshold usingResque.logger.level
:
# config/initializers/resque.rbResque.logger.level=Logger::DEBUG
If you want Resque to log to a file, in Rails do:
# config/initializers/resque.rbResque.logger=Logger.new(Rails.root.join('log',"#{Rails.env}_resque.log"))
If you're running multiple, separate instances of Resque you may wantto namespace the keyspaces so they do not overlap. This is not unlikethe approach taken by many memcached clients.
This feature is provided by theredis-namespace library, whichResque uses by default to separate the keys it manages from other keysin your Redis server.
Simply use theResque.redis.namespace
accessor:
Resque.redis.namespace="resque:GitHub"
We recommend sticking this in your initializer somewhere after Redisis configured.
Resque allows to store count of processed and failed jobs.
By default it will store it in Redis using the keysstats:processed
andstats:failed
.
Some apps would want another stats store, or even a null store:
# config/initializers/resque.rbclassNullDataStoredefstat(stat)0enddefincrement_stat(stat,by)enddefdecrement_stat(stat,by)enddefclear_stat(stat)endendResque.stat_data_store=NullDataStore.new
For a list of available plugins seehttps://github.com/resque/resque/wiki/plugins.
If you'd like to write your own plugin, or want to customize Resqueusing hooks (such asResque.after_fork
), seedocs/HOOKS.md.
How does Resque compare to DelayedJob, and why would you choose oneover the other?
- Resque supports multiple queues
- DelayedJob supports finer grained priorities
- Resque workers are resilient to memory leaks / bloat
- DelayedJob workers are extremely simple and easy to modify
- Resque requires Redis
- DelayedJob requires ActiveRecord
- Resque can only place JSONable Ruby objects on a queue as arguments
- DelayedJob can placeany Ruby object on its queue as arguments
- Resque includes a Sinatra app for monitoring what's going on
- DelayedJob can be queried from within your Rails app if you want toadd an interface
If you're doing Rails development, you already have a database andActiveRecord. DelayedJob is super easy to setup and works great.GitHub used it for many months to process almost 200 million jobs.
Choose Resque if:
- You need multiple queues
- You don't care / dislike numeric priorities
- You don't need to persist every Ruby object ever
- You have potentially huge queues
- You want to see what's going on
- You expect a lot of failure / chaos
- You can setup Redis
- You're not running short on RAM
Choose DelayedJob if:
- You like numeric priorities
- You're not doing a gigantic amount of jobs each day
- Your queue stays small and nimble
- There is not a lot failure / chaos
- You want to easily throw anything on the queue
- You don't want to setup Redis
In no way is Resque a "better" DelayedJob, so make sure you pick thetool that's best for your app.
On certain platforms, when a Resque worker reserves a job itimmediately forks a child process. The child processes the job thenexits. When the child has exited successfully, the worker reservesanother job and repeats the process.
Why?
Because Resque assumes chaos.
Resque assumes your background workers will lock up, run too long, orhave unwanted memory growth.
If Resque workers processed jobs themselves, it'd be hard to whip theminto shape. Let's say one is using too much memory: you send it asignal that says "shutdown after you finish processing the currentjob," and it does so. It then starts up again - loading your entireapplication environment. This adds useless CPU cycles and causes adelay in queue processing.
Plus, what if it's using too much memory and has stopped responding tosignals?
Thanks to Resque's parent / child architecture, jobs that use too much memoryrelease that memory upon completion. No unwanted growth.
And what if a job is running too long? You'd need tokill -9
it thenstart the worker again. With Resque's parent / child architecture youcan tell the parent to forcefully kill the child then immediatelystart processing more jobs. No startup delay or wasted cycles.
The parent / child architecture helps us keep tabs on what workers aredoing, too. By eliminating the need tokill -9
workers we can haveparents remove themselves from the global listing of workers. If wejust ruthlessly killed workers, we'd need a separate watchdog processto add and remove them to the global listing - which becomescomplicated.
Workers instead handle their own state.
Resque usesKernel#exit!
for exiting its workers' child processes. So anyat_exit
callback defined in your application won't be executed when the job is finished and the child process exits.
You can alter this behavior by setting theRUN_AT_EXIT_HOOKS
environment variable.
Here's a parent / child pair doing some work:
$ ps -e -o pid,command | grep [r]esque92099 resque: Forked 92102 at 125314276992102 resque: Processing file_serve since 1253142769
You can clearly see that process 92099 forked 92102, which has beenworking since 1253142769.
(By advertising the time they began processing you can easily use monitor god to kill stale workers.)
When a parent process is idle, it lets you know what queues it iswaiting for work on:
$ ps -e -o pid,command | grep [r]esque92099 resque: Waiting for file_serve,warm_cache
Resque workers respond to a few different signals:
QUIT
- Wait for child to finish processing then exitTERM
/INT
- Immediately kill child then exitUSR1
- Immediately kill child but don't exitUSR2
- Don't start to process any new jobsCONT
- Start to process new jobs again after a USR2
If you want to gracefully shutdown a Resque worker, useQUIT
.
If you want to kill a stale or stuck child, useUSR1
. Processingwill continue as normal unless the child was not found. In that caseResque assumes the parent process is in a bad state and shuts down.
If you want to kill a stale or stuck child and shutdown, useTERM
If you want to stop processing jobs, but want to leave the worker running(for example, to temporarily alleviate load), useUSR2
to stop processing,thenCONT
to start it again. It's also possible topause all workers.
When shutting down processes, Heroku sends every process a TERM signal at thesame time. By default this causes an immediate shutdown of any running jobleading to frequentResque::TermException
errors. For short running jobs, a simplesolution is to give a small amount of time for the job to finishbefore killing it.
Resque doesn't handle this out of the box (for both cedar-14 and heroku-16), you need toinstall theresque-heroku-signals
addon which adds the required signal handling to make the behavior described above work.Related issue:#1559
To accomplish this set the following environment variables:
RESQUE_PRE_SHUTDOWN_TIMEOUT
- The time between the parent receiving a shutdown signal (TERM by default) and it sending that signal on to the child process. Designed to give the child processtime to complete before being forced to die.TERM_CHILD
- Must be set forRESQUE_PRE_SHUTDOWN_TIMEOUT
to be used. After the timeout, if the child is still running it will raise aResque::TermException
and exit.RESQUE_TERM_TIMEOUT
- By default you have a few seconds to handleResque::TermException
in your job.RESQUE_TERM_TIMEOUT
andRESQUE_PRE_SHUTDOWN_TIMEOUT
must be lower than theheroku dyno timeout.
Workers will not process pending jobs if the Redis keypause-all-workers
is set with the string value "true".
Resque.redis.set('pause-all-workers','true')
Nothing happens to jobs that are already being processed by workers.
Unpause by removing the Redis keypause-all-workers
.
Resque.redis.del('pause-all-workers')
If you're using god to monitor Resque, we have provided exampleconfigs inexamples/god/
. One is for starting / stopping workers,the other is for killing workers that have been running too long.
If you're using monit,examples/monit/resque.monit
is provided freeof charge. This isnot used by GitHub in production, so pleasesend patches for any tweaks or improvements you can make to it.
If your workers remain idle for too long they may lose their MySQL connection. Depending on your version of Rails, we recommend the following:
In yourperform
method, add the following line:
classMyTaskdefself.performActiveRecord::Base.verify_active_connections!# rest of your codeendend
The Rails doc says the following aboutverify_active_connections!
:
Verify active connections and remove and disconnect connections associated with stale threads.
In yourperform
method, instead ofverify_active_connections!
, use:
classMyTaskdefself.performActiveRecord::Base.clear_active_connections!# rest of your codeendend
From the Rails docs onclear_active_connections!
:
Returns any connections in use by the current thread back to the pool, and also returns connections to the pool cached by threads that are no longer alive.
Want to hack on Resque?
First clone the repo and run the tests:
git clone git://github.com/resque/resque.gitcd resquerake test
If the tests do not pass make sure you have Redis installedcorrectly (though we make an effort to tell you if we feel this is thecase). The tests attempt to start an isolated instance of Redis torun against.
Also make sure you've installed all the dependencies correctly. Forexample, try loading theredis-namespace
gem after you've installedit:
$ irb>> require 'rubygems'=> true>> require 'redis/namespace'=> true
If you get an error requiring any of the dependencies, you may havefailed to install them or be seeing load path issues.
Resque ships with a demo Sinatra app for creating jobs that are laterprocessed in the background.
Try it out by looking at the README, found atexamples/demo/README.markdown
.
ReadCONTRIBUTING.md first.
Once you've made your great commits:
- Fork Resque
- Create a topic branch -
git checkout -b my_branch
- Push to your branch -
git push origin my_branch
- Create aPull Request from your branch
Please add them to theFAQ or open an issue on this repo.
- Code:
git clone git://github.com/resque/resque.git
- Home:http://github.com/resque/resque
- Docs:http://rubydoc.info/gems/resque
- Bugs:http://github.com/resque/resque/issues
- Gems:https://rubygems.org/gems/resque
This project usesSemantic Versioning
Chris Wanstrath ::chris@ozmm.org :: @defunkt
About
Resque is a Redis-backed Ruby library for creating background jobs, placing them on multiple queues, and processing them later.