resque-scheduler is a free, open source orchestration & scheduling project written in Ruby and released under MIT. It has 1,746 GitHub stars, 477 forks and 87 open issues, and was last pushed 2 months ago. On this registry it ranks #51 of 64 tracked projects in Orchestration & Scheduling, with 5 head-to-head comparisons available.

What is resque-scheduler?

resque-scheduler is a light-weight job scheduling system built on top of Resque, an extension that adds support for queueing items in the future for Ruby applications that already run background jobs through Resque.

What it is

resque-scheduler is an extension to Resque that adds support for queueing items in the future. It lives in the Ruby background-job ecosystem and requires the same infrastructure Resque itself requires, including the Redis connection that Resque is configured with, because the scheduler needs to know everything Resque needs to know. Every job it triggers is an ordinary Resque job pushed onto an ordinary Resque work queue, which means it sits alongside an existing Resque deployment rather than replacing the job runner.

The concrete problem it solves is the absence of timing in Resque. Plain Resque has no notion of running something later or running something on a recurring basis. resque-scheduler fills that gap in two distinct ways: recurring (scheduled) jobs, which behave like cron jobs and repeat on a regular basis, and delayed jobs, which are Resque jobs to be run at some point in the future. It replaces the hand-rolled cron entry, sleep loop, or bespoke timestamp-checking worker that teams otherwise write to get periodic and deferred work into Resque.

Key capabilities

  • Delayed jobs through Resque.enqueue_in(5.days, SendFollowupEmail, argument) and Resque.enqueue_at(5.days.from_now, SomeJob, argument).
  • Recurring scheduled jobs defined as a hash, usually loaded from YAML with Resque.schedule = YAML.load_file('your_resque_schedule.yml').
  • One scheduler process that both queues items from the schedule and polls the delayed queue for items ready to be pushed onto work queues.
  • Rake integration by requiring resque/scheduler/tasks, with the scheduler task depending on resque:setup.
  • A standalone resque-scheduler executable, installed with the gem, that accepts options via flags as well as environment variables and responds to resque-scheduler --help.
  • Environment variables including APP_NAME, RAILS_ENV, INITIALIZER_PATH, DYNAMIC_SCHEDULE, and BACKGROUND for running in the background via Process.daemon where supported.
  • Dynamic schedules via Resque::Scheduler.set_schedule and remove_schedule, available in 2.0.0 and later, where the scheduler process looks for schedule changes and applies them on the fly.

Who uses it and how

  • Ruby and Rails applications already using Resque for background work that need cron-like recurring jobs or deferred execution without adding a separate scheduler stack.
  • Deployments that run a dedicated scheduler process, started with rake resque:scheduler or rake environment resque:scheduler when the environment must be loaded first; this process never exits by design.
  • Daemonized or background deployments that set BACKGROUND to a non-empty value and let the scheduler detach through Process.daemon.
  • Teams that need to change the schedule at runtime and uncomment Resque::Scheduler.dynamic = true so the scheduler applies schedule updates on the fly.
  • Setups where each schedule entry already sets its own queue, so job classes do not need to be required in the scheduler configuration.

Getting started

Install with gem install resque-scheduler, or add gem 'resque-scheduler' to the Gemfile, then run the scheduler with rake resque:scheduler.

How it compares

The facts name no paid products that this project replaces, and no competing scheduling tool besides Resque itself, the parent project it extends. Within this registry it is a companion to Resque rather than a general-purpose scheduler, so the comparison that matters is against whatever ad hoc cron or custom polling code a Resque team would write instead.

When to use it — and when not to

A self-hoster must operate Redis and Resque as well as a long-running scheduler process that never exits, because the scheduler is not a one-shot job. It is the wrong choice for projects that do not already run Resque, for non-Ruby stacks, and for anyone wanting a managed scheduling service. The README covers what most people need but points to rubydoc for individual method details, the excerpt itself is truncated mid-list of environment variables, and the repository carries 87 open issues, so pin a known version and read the changelog before upgrading across major releases.

project readme (upstream, from github) — read inline

resque-scheduler

Gem Version Ruby specs Code Climate

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 (via Process.daemon, if supported) (default false)
  • DYNAMIC_SCHEDULE - Enables dynamic scheduling if non-empty (default false)
  • RAILS_ENV - Environment to use in procline ($0) (default empty)
  • INITIALIZER_PATH - Path to a Ruby file that will be loaded before requiring resque and resque/scheduler (default empty).
  • RESQUE_SCHEDULER_INTERVAL - Interval in seconds for checking if a scheduled job must run (coerced with Kernel#Float()) (default 5)
  • 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 of MonoLogger::FATAL, default false)
  • VERBOSE - Maximize log verbosity if non-empty (equivalent to a level of MonoLogger::DEBUG, default false)

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

readme truncated — read the full docs on github

Frequently asked questions

Is resque-scheduler free to use?

resque-scheduler is open source under the MIT licence. There is no licence fee and no seat count — you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does resque-scheduler do?

A light-weight job scheduling system built on top of Resque

What is resque-scheduler written in?

resque-scheduler is primarily written in Ruby. Its source is publicly available at https://github.com/resque/resque-scheduler, and it has 1,746 GitHub stars.