Scheduled Jobs and Custom Clock Processes in Ruby with Clockwork

Last Updated: 10 January 2014

clock clockwork ruby scheduling

Table of Contents

Clock process is a type of dyno counting toward your dyno-hours that you will be charged for each month.

The ability to schedule background jobs is a requirement for most modern web apps. These jobs might be user-oriented, like sending emails; administrative, like taking backups or synchronizing data; or even a more integral part of the app itself.

On a single server deployment a system level tool like cron is the obvious choice to accomplish this kind of scheduling. However, when deploying to a cloud platform like Heroku, something higher level is required since instances of the application will be running in a distributed environment where machine-local tools are not useful.

The Heroku Scheduler add-on is a fantastic solution for simple tasks that need to run at 10 minute, hourly, or daily intervals (or multiples of those intervals). But what about tasks that need to run every 5 minutes or 37 minutes or those that need to run at a very specific time? For these more unique and complicated use cases running your own scheduling process can be very useful.

If you have questions about Ruby on Heroku, consider discussing it in the Ruby on Heroku forums.

Clockwork

There are a few Ruby language scheduling libraries to choose from. The rufus-scheduler gem has been around for a few years and is still popular. The resque-scheduler extension is also quite popular, but it is specific to the Resque background job library.

For this example, we’re going to use clockwork, a lightweight, generic scheduling gem. It provides a clean, easy-to-use scheduling DSL, has few dependencies and is not tied to any specific job queuing system.

Install clockwork by adding it to your Gemfile and running bundle install:

gem 'clockwork'

Execution schedule

Heroku's runtime servers express time in UTC. You should use the same for your schedules.

Next you’ll need to author the file to define your schedule. The clockwork README has a lot of great examples that show the flexibility of the library.

For this example, we’re going to queue jobs in a Rails app using the Delayed Job queuing system. Here’s the lib/clock.rb example file:

require File.expand_path('../../config/boot',        __FILE__)
require File.expand_path('../../config/environment', __FILE__)
require 'clockwork'

include Clockwork

every(4.minutes, 'Queueing interval job') { Delayed::Job.enqueue IntervalJob.new }
every(1.day, 'Queueing scheduled job', :at => '14:17') { Delayed::Job.enqueue ScheduledJob.new }

Here we’ve configured clockwork to queue background jobs in 2 different ways. The first directive will queue an IntervalJob every 4 minutes, starting at the time the clock process is launched. The second will queue a ScheduledJob once per day only at 2:17 PM.

Clock process type

Finally, you’ll need to define a process type in the Procfile. In this example we’ll call the process clock, so the Procfile should look something like this:

clock: bundle exec clockwork lib/clock.rb

Deployment

Commit the Gemfile, Procfile, and clock.rb changes and redeploy your application.

The final step is to scale up the clock process. This is a singleton process, meaning you’ll never need to scale up more than 1 of these processes. If you run two, the work will be duplicated.

$ heroku ps:scale clock=1

You should see similar output to the following in your Heroku logs.

2012-05-30T20:59:38+00:00 heroku[clock.1]: State changed from created to starting
2012-05-30T20:59:38+00:00 heroku[api]: Scale to clock=1, web=3 by user@heroku.com
2012-05-30T20:59:40+00:00 heroku[clock.1]: Starting process with command `bundle exec clockwork lib/clock.rb`
2012-05-30T20:59:41+00:00 heroku[clock.1]: State changed from starting to up
2012-05-30T20:59:48+00:00 app[clock.1]: Starting clock for 1 events: [ Queueing interval job ]
2012-05-30T20:59:48+00:00 app[clock.1]: Queuing scheduled jobs

Now you have a custom clock process up and running. Check out the clockwork README for more options, including scheduling conditionally with :if and lambda functions.