Sidetiq ======= [![Build Status](https://travis-ci.org/tobiassvn/sidetiq.png)](https://travis-ci.org/tobiassvn/sidetiq) [![Dependency Status](https://gemnasium.com/tobiassvn/sidetiq.png)](https://gemnasium.com/tobiassvn/sidetiq) Recurring jobs for [Sidekiq](http://mperham.github.com/sidekiq/). Table Of Contents ----------------- * [Overview](#section_Overview) * [Dependencies](#section_Dependencies) * [Installation](#section_Installation) * [Introduction](#section_Introduction) * [Backfills](#section_Backfills) * [Configuration](#section_Configuration) * [Logging](#section_Configuration_Logging) * [API](#section_API) * [Polling](#section_Polling) * [Known Issues](#section_Known_Issues) * [Web Extension](#section_Web_Extension) * [Contribute](#section_Contribute) * [License](#section_License) * [Author](#section_Author) Overview -------- Sidetiq provides a simple API for defining recurring workers for Sidekiq. - Flexible DSL based on [ice_cube](http://seejohnrun.github.com/ice_cube/) - Sidetiq uses a locking mechanism (based on `setnx` and `pexpire`) internally so Sidetiq clocks can run in each Sidekiq process without interfering with each other (tested with sub-second polling of scheduled jobs by Sidekiq and Sidetiq clock rates above 100hz). Detailed API documentation is available on [rubydoc.info](http://rdoc.info/github/tobiassvn/sidetiq/). Dependencies ------------ - [Sidekiq](http://mperham.github.com/sidekiq/) - [ice_cube](http://seejohnrun.github.com/ice_cube/) Installation ------------ The best way to install Sidetiq is with RubyGems: $ [sudo] gem install sidetiq If you're installing from source, you can use [Bundler](http://gembundler.com/) to pick up all the gems ([more info](http://gembundler.com/bundle_install.html)): $ bundle install Introduction ------------ Defining recurring jobs is simple: ```ruby class MyWorker include Sidekiq::Worker include Sidetiq::Schedulable # Daily at midnight tiq { daily } def perform # do stuff ... end end ``` It also is possible to define multiple scheduling rules for a worker: ```ruby class MyWorker include Sidekiq::Worker include Sidetiq::Schedulable tiq do # Every third year in March yearly(3).month_of_year(:march) # Every second year in February yearly(2).month_of_year(:february) end def perform # do stuff ... end end ``` Or complex schedules: ```ruby class MyWorker include Sidekiq::Worker include Sidetiq::Schedulable # Every other month on the first monday and last tuesday at 12 o'clock. tiq { monthly(2).day_of_week(1 => [1], 2 => [-1]).hour_of_day(12) } def perform # do stuff ... end end ``` Additionally, the last and current occurrence time (as a `Float`) can be passed to the worker simply by adding arguments to `#perform`. Sidetiq will check the method arity before enqueuing the job: ```ruby class MyWorker include Sidekiq::Worker include Sidetiq::Schedulable tiq { daily } # Receive last and current occurrence times. def perform(last_occurrence, current_occurrence) # do stuff ... end end ``` To start Sidetiq, simply call `Sidetiq::Clock.start!` in a server specific configuration block: ```ruby Sidekiq.configure_server do |config| Sidetiq::Clock.start! end ``` Additionally, Sidetiq includes a middleware that will check if the clock thread is still alive and restart it if necessary. Backfills --------- In certain cases it is desirable that missed jobs will be enqueued retroactively, for example when a critical, hourly job isn't run due to server downtime. To solve this, `#tiq` takes a *backfill* option. If missing job occurrences have been detected, Sidetiq will then enqueue the jobs automatically. It will also ensure that the timestamps passed to `#perform` are as expected: ```ruby class MyWorker include Sidekiq::Worker include Sidetiq::Schedulable tiq backfill: true do hourly end def perform(last_occurrence, current_occurrence) # do stuff ... end end ``` Configuration ------------- ```ruby Sidetiq.configure do |config| # Thread priority of the clock thread (default: Thread.main.priority as # defined when Sidetiq is loaded). config.priority = 2 # Clock tick resolution in seconds (default: 1). config.resolution = 0.5 # Clock locking key expiration in ms (default: 1000). config.lock_expire = 100 # When `true` uses UTC instead of local times (default: false) config.utc = false end ``` ### Logging By default Sidetiq uses Sidekiq's logger. However, this is configuration: ```ruby Sidetiq.logger = Logger.new(STDOUT) ``` The logger should implement Ruby's [Logger API](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html). API --- Sidetiq implements a simple API to support reflection of recurring jobs at runtime: `Sidetiq.schedules` returns a `Hash` with the `Sidekiq::Worker` class as the key and the Sidetiq::Schedule object as the value: ```ruby Sidetiq.schedules # => { MyWorker => # } ``` `Sidetiq.workers` returns an `Array` of all workers currently tracked by Sidetiq (workers which include `Sidetiq::Schedulable` and a `.tiq` call): ```ruby Sidetiq.workers # => [MyWorker, AnotherWorker] ``` `Sidetiq.scheduled` returns an `Array` of currently scheduled Sidetiq jobs as `Sidekiq::SortedEntry` (`Sidekiq::Job`) objects. Optionally, it is possible to pass a block to which each job will be yielded: ```ruby Sidetiq.scheduled do |job| # do stuff ... end ``` This list can further be filtered by passing the worker class to `#scheduled`, either as a String or the constant itself: ```ruby Sidetiq.scheduled(MyWorker) do |job| # do stuff ... end ``` The same can be done for recurring jobs currently scheduled for retries (`.retries` wraps `Sidekiq::RetrySet` instead of `Sidekiq::ScheduledSet`): ```ruby Sidetiq.retries(MyWorker) do |job| # do stuff ... end ``` Polling ------- By default Sidekiq uses a 15 second polling interval to check if scheduled jobs are due. If a recurring job has to run more often than that you should lower this value. ```ruby Sidekiq.options[:poll_interval] = 1 ``` More information about this can be found in the [Sidekiq Wiki](https://github.com/mperham/sidekiq/wiki/Scheduled-Jobs). Known Issues ------------ Unfortunately, using ice_cube's interval methods is terribly slow on start-up (it tends to eat up 100% CPU for quite a while). This is due to it calculating every possible occurrence since the schedule's start time. The way around is to avoid using them. For example, instead of defining a job that should run every 15 minutes like this: ```ruby class MyWorker include Sidekiq::Worker include Sidetiq::Schedulable tiq { minutely(15) } end ``` It is better to use the more explicit way: ```ruby class MyWorker include Sidekiq::Worker include Sidetiq::Schedulable tiq { hourly.minute_of_hour(0, 15, 30, 45) } end ``` Web Extension ------------- Sidetiq includes an extension for Sidekiq's web interface. It will not be loaded by default, so it will have to be required manually: ```ruby require 'sidetiq/web' ``` ### SCREENSHOT ![Screenshot](http://f.cl.ly/items/1P2u1v091F3V1n381g2I/Screen%20Shot%202013-02-01%20at%2012.16.17.png) Contribute ---------- If you'd like to contribute to Sidetiq, start by forking my repo on GitHub: [http://github.com/tobiassvn/sidetiq](http://github.com/tobiassvn/sidetiq) To get all of the dependencies, install the gem first. The best way to get your changes merged back into core is as follows: 1. Clone down your fork 1. Create a thoughtfully named topic branch to contain your change 1. Write some code 1. Add tests and make sure everything still passes by running `rake` 1. If you are adding new functionality, document it in the README 1. Do not change the version number, I will do that on my end 1. If necessary, rebase your commits into logical chunks, without errors 1. Push the branch up to GitHub 1. Send a pull request to the tobiassvn/sidetiq project. License ------- Sidetiq is released under the 3-clause BSD. See LICENSE for further details. Author ------ Tobias Svensson, [@tobiassvn](https://twitter.com/tobiassvn), [http://github.com/tobiassvn](http://github.com/tobiassvn)