resque-scheduler
Description
Resque-scheduler is an extension to Resque that adds support for queueing items in the future.
Job scheduling is supported in two different ways: Recurring (scheduled) and Delayed.
Scheduled jobs are like cron jobs, recurring on a regular basis. Delayed jobs are resque jobs that you want to run at some point in the future. The syntax is pretty explanatory:
Resque.enqueue_in(5.days, SendFollowupEmail, argument) # runs a job in 5 days, calling SendFollowupEmail.perform(argument)
# or
Resque.enqueue_at(5.days.from_now, SomeJob, argument) # runs a job at a specific time, calling SomeJob.perform(argument)
Documentation
This README covers what most people need to know. If you're looking
for details on individual methods, you might want to try the
rubydoc.
Installation
To install:
gem install resque-scheduler
If you use a Gemfile:
gem 'resque-scheduler'
Adding the resque:scheduler rake task:
require 'resque/scheduler/tasks'
Rake integration
By default, resque-scheduler depends on the "resque:setup" rake task.
Since you probably already have this task, lets just put our
configuration there. resque-scheduler pretty much needs to know
everything resque needs to know.
# Resque tasks
require 'resque/tasks'
require 'resque/scheduler/tasks'
namespace :resque do
task :setup do
require 'resque'
# you probably already have this somewhere
Resque.redis = 'localhost:6379'
end
task :setup_schedule => :setup do
require 'resque-scheduler'
# If you want to be able to dynamically change the schedule,
# uncomment this line. A dynamic schedule can be updated via the
# Resque::Scheduler.set_schedule (and remove_schedule) methods.
# When dynamic is set to true, the scheduler process looks for
# schedule changes and applies them on the fly.
# Note: This feature is only available in >=2.0.0.
# Resque::Scheduler.dynamic = true
# The schedule doesn't need to be stored in a YAML, it just needs to
# be a hash. YAML is usually the easiest.
Resque.schedule = YAML.load_file('your_resque_schedule.yml')
# If your schedule already has +queue+ set for each job, you don't
# need to require your jobs. This can be an advantage since it's
# less code that resque-scheduler needs to know about. But in a small
# project, it's usually easier to just include your job classes here.
# So, something like this:
require 'jobs'
end
task :scheduler => :setup_schedule
end
The scheduler rake task is responsible for both queueing items from the schedule and polling the delayed queue for items ready to be pushed on to the work queues. For obvious reasons, this process never exits.
rake resque:scheduler
or, if you want to load the environment first:
rake environment resque:scheduler
Standalone Executable
The scheduler may also be run via a standalone resque-scheduler
executable, which will be available once the gem is installed.
# Get some help
resque-scheduler --help
The executable accepts options via option flags as well as via environment variables.
Environment Variables
Both the Rake task and standalone executable support the following environment variables:
APP_NAME- Application name used in procline ($0) (default empty)BACKGROUND- Run in the background if non-empty (viaProcess.daemon, if supported) (defaultfalse)DYNAMIC_SCHEDULE- Enables dynamic scheduling if non-empty (defaultfalse)RAILS_ENV- Environment to use in procline ($0) (default empty)INITIALIZER_PATH- Path to a Ruby file that will be loaded before requiringresqueandresque/scheduler(default empty).RESQUE_SCHEDULER_INTERVAL- Interval in seconds for checking if a scheduled job must run (coerced withKernel#Float()) (default5)LOGFILE- Log file name (default empty, meaning$stdout)LOGFORMAT- Log output format to use (either'text','json'or'logfmt', default'text')PIDFILE- If non-empty, write process PID to file (default empty)QUIET- Silence most output if non-empty (equivalent to a level ofMonoLogger::FATAL, defaultfalse)VERBOSE- Maximize log verbosity if non-empty (equivalent to a level ofMonoLogger::DEBUG, defaultfalse)
Resque Pool integration
For normal work with the
resque-pool gem, add the
following task to wherever tasks are kept, such as
./lib/tasks/resque.rake:
task 'resque:pool:setup' do
Resque::Pool.after_prefork do |job|
Resque.redis.reconnect
end
end
Delayed jobs
Delayed jobs are one-off jobs that you want to be put into a queue at some point in the future. The classic example is sending email:
Resque.enqueue_in(
5.days,
SendFollowUpEmail,
user_id: current_user.id
)
This will store the job for 5 days in the resque delayed queue at which time the scheduler process will pull it from the delayed queue and put it in the appropriate work queue for the given job and it will be processed as soon as a worker is available (just like any other resque job).
NOTE: The job does not fire exactly at the time supplied. Rather, once that time is in the past, the job moves from the delayed queue to the actual resque work queue and will be completed as workers are free to process it.
Also supported is Resque.enqueue_at which takes a timestamp to queue the
job, and Resque.enqueue_at_with_queue which takes both a timestamp and a
queue name:
Resque.enqueue_at_with_queue(
'queue_name',
5.days.from_now,
SendFollowUpEmail,
user_id: current_user.id
)
The delayed queue is stored in redis and is persisted in the same way the
standard resque jobs are persisted (redis writing to disk). Delayed jobs differ
from scheduled jobs in that if your scheduler process is down or workers are
down when a particular job is supposed to be queue, they will simply "catch up"
once they are started again. Jobs are guaranteed to run (provided they make it
into the delayed queue) after their given queue_at time has passed.
One other thing to note is that insertion into the delayed queue is O(log(n)) since the jobs are stored in a redis sorted set (zset). I can't imagine this being an issue for someone since redis is stupidly fast even at log(n), but full disclosure is always best.
Removing Delayed Jobs
If you have the need to cancel a delayed job, you can do like so:
# after you've enqueued a job like:
Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :user_id => current_user.id)
# remove the job with exactly the same parameters:
Resque.remove_delayed(SendFollowUpEmail, :user_id => current_user.id)
If you need to cancel a delayed job based on some matching arguments, but don't wish to specify each argument from when the job was created, you can do like so:
# after you've enqueued a job like:
Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :account_id => current_account.id, :user_id => current_user.id)
# remove jobs matching just the account:
Resque.remove_delayed_selection { |args| args[0]['account_id'] == current_account.id }
# or remove jobs matching just the user:
Resque.remove_delayed_selection { |args| args[0]['user_id'] == current_user.id }
If you need to cancel a delayed job based on some matching arguments AND by which class the job is, but don't wish to specify each argument from when the job was created, you can do like so:
# after you've enqueued a job like:
Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :account_id => current_account.id, :user_id => current_user.id)
# remove jobs matching just the account and that were of the class SendFollowUpEmail:
Resque.remove_delayed_selection(SendFollowUpEmail) { |args| args[0]['account_id'] == current_account.id }
# or remove jobs matching just the user and that were of the class SendFollowUpEmail:
Resque.remove_delayed_selection(SendFollowUpEmail) { |args| args[0]['user_id'] == current_user.id }
If you need to enqueue immediately a delayed job based on some matching arguments, but don't wish to specify each argument from when the job was created, you can do like so:
## after you've enqueued a job like:
Resque.enq