How to run periodic jobs with Sidekiq for free
One of the features not included in the free version of Sidekiq is running periodic jobs (or cron jobs or recurring jobs). There are gems that offer this functionality as an add-on, but it turns out that it can be implemented in less that 40 lines of code, so there is no need to pull an additional bloated dependency.
All we need is a Scheduler worker that when you run it:
- It knows when to run other workers.
- It re-schedules itself to be executed after some time (1 minute, 10 minutes, 1hour, … whatever is necessary).
Also, we need to kick off the Scheduler worker for the first time in the Sidekiq initializer.
Below you can find the implementation for the SchedulerWorker class and the changes to the Sidekiq initializer. With the current implementation of the SchedulerWorker it will run every minute and check if there are workers that must be run or not. The workers are configured in the SCHEDULE hash, containing the periodic workers as keys and the values are lambdas that return true when the worker must be run. For instance, in the code below, FirstWorker will run 5 minutes after the hour every hour. SecondWorker will run every day at 9AM. ThirdWorker will run every 10 minutes.
Enjoy.
# app/workers/scheduler_worker.rb
class SchedulerWorker
include Sidekiq::Worker
sidekiq_options queue: 'critical'
SCHEDULE = {
FirstWorker => -> (time) { time.min == 5 },
SecondWorker => -> (time) { time.min == 0 && time.hour == 9 },
ThirdWorker => -> (time) { time.min % 10 == 0 },
}
def perform
execution_time = Time.zone.now
execution_time -= execution_time.sec
self.class.perform_at(execution_time + 60) unless scheduled?
SCHEDULE.each do |(worker_class, schedule_lambda)|
worker_class.perform_async if !scheduled?(worker_class) && schedule_lambda.call(execution_time)
end
end
def scheduled?(worker_class = self.class)
scheduled_workers[worker_class.name]
end
private
def scheduled_workers
@scheduled_workers ||= Sidekiq::ScheduledSet.new.entries.each_with_object({}) do |item, hash|
hash[item['class']] = true
end
end
end
# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
config.on(:startup) do
SchedulerWorker.perform_async unless SchedulerWorker.new.scheduled?
end
end