今回は定期実行処理を行うgem「sidekiq-cron」のREADMEを読んでみたので、翻訳しつつその内容を備忘録としてまとめました。
Sidekiq-Cron
Sidekiqのスケジューリング拡張機能
Sidekiq-CronはSidekiqワーカーと一緒にスレッドを実行して、指定された時間にジョブをスケジューリングします(cron表記* * * * * Fugitによって解析されます)。
30秒ごとにスケジューリングする新しいジョブをチェックし、複数のSidekiqワーカーが実行されているときに同じジョブを複数回スケジューリングしないようにします。
スケジューリングジョブは少なくとも1つのSidekiqプロセスが実行されている場合にのみ追加されますが、複数のSidekiqプロセスまたはノードが実行されている環境ではSidekiq-Cronを使用しても安全です。
スケジューリングがどのように機能するかを知りたい場合は内部をチェックしてください。
ActiveJob(Rails 4.2以降)で動作します。
Sidekiq PROは必要ありません。このgemは元のSidekiqで使用できます。
0.6以下から1.0へのアップグレード
Sidekiq-Cron < 1.0はrufus-scheduler < 3.5に依存していたことに注意してください。 rufus-scheduler >= 3.5でこれらの古いバージョンを使用すると、ジョブの作成に失敗します。 Sidekiq-Cron1.0には、rufus-schedulerからrufus-schedulerのコア依存関係であるfugitに切り替えるパッチが含まれています。
変更ログ
新しいバージョンにアップグレードする前に、変更ログをお読みください。
インストール
必要条件
- Redis2.8以降が必要(大規模な使用にはRedis 3.0.3以降がおすすめです)
- Sidekiq4.2以降が必要(Sidekiq < 4の場合はバージョンsidekiq-cron0.3.1を使用してください)
- Sidekiq6.5にはSidekiq-Cron1.5+が必要
gemのインストール
$ gem install sidekiq-cron
または、Gemfileに追加してbundle installを実行します。
gem "sidekiq-cron"
使い方
ジョブのプロパティ
{
'name' => 'name_of_job', # must be uniq!
'cron' => '1 * * * *', # execute at 1 minute of every hour, ex: 12:01, 13:01, 14:01, 15:01, ... (HH:MM)
'class' => 'MyClass',
# OPTIONAL
'queue' => 'name of queue',
'args' => '[Array or Hash] of arguments which will be passed to perform method',
'date_as_argument' => true, # add the time of execution as last argument of the perform method
'active_job' => true, # enqueue job through rails 4.2+ active job interface
'queue_name_prefix' => 'prefix', # rails 4.2+ active job queue with prefix
'queue_name_delimiter' => '.', # rails 4.2+ active job queue with custom delimiter
'description' => 'A sentence describing what work this job performs.'
'status' => 'disabled' # default: enabled
}
Time, cron and Sidekiq-Cron
cron表記をテストするには、crontab.guruを使用できます。
Sidekiq-CronはFugitを使用してcronlineを解析します。したがって、許可される形式の詳細については、Fugitのドキュメントを確認してください。
Railsを使用している場合、これはRailsで構成されているタイムゾーンに対して評価されます。それ以外の場合、デフォルトはUTCです。
別のタイムゾーンに基づいてジョブをキューに入れたい場合は、「0 22 * * 1-5 America / Chicago
」のように、cronlineでタイムゾーンを指定できます。
Second-precision(1分未満)のcronlines
標準の5パラメーターcronline形式に加えて、sidekiq-cronは、変更された6パラメーターcronline形式を使用したsecond-precisionのジョブのスケジューリングをサポートします。
Seconds Minutes Hours Days Months DayOfWeek
例: "* / 30 * * * * *"
は、ジョブが30秒ごとに実行されるようにスケジュールします。
second-precisionでジョブをスケジュールする場合は、デフォルトのスケジュールポーリング間隔をオーバーライドして、ジョブの間隔よりも短くする必要がある場合があることに注意してください。
Sidekiq.options[:average_scheduled_poll_interval] = 10
書き込み時のデフォルト値は30秒です。詳細については、内部を参照してください。
スケジュールできるオブジェクト/クラス
Sidekiq Worker
この例では、次のようなHardWorkerを使用しています。
class HardWorker
include Sidekiq::Worker
def perform(*args)
# do something
end
end
Active Job Worker
次のようなExampleJobをスケジュールできます。
class ExampleJob < ActiveJob::Base
queue_as :default
def perform(*args)
# Do something
end
end
Active Jobsの場合Sidekiq::Cron::Job.create
またはハッシュ構成でsymbolize_args: true
を使用できます。これにより、渡された引数がワーカーでperfrom
メソッドが戻されたときにシンボル化されます。
Adding Cron job
class HardWorker
include Sidekiq::Worker
def perform(name, count)
# do something
end
end
Sidekiq::Cron::Job.create(name: 'Hard worker - every 5min', cron: '*/5 * * * *', class: 'HardWorker') # execute at every 5 minutes, ex: 12:05, 12:10, 12:15...etc
# => true
create
メソッドはジョブが保存されているかどうかにかかわらず、true/falseのみを返します。
job = Sidekiq::Cron::Job.new(name: 'Hard worker - every 5min', cron: '*/5 * * * *', class: 'HardWorker')
if job.valid?
job.save
else
puts job.errors
end
# or simple
unless job.save
puts job.errors # will return array of errors
end
ハッシュからより多くのジョブを読み込む:
hash = {
'name_of_job' => {
'class' => 'MyClass',
'cron' => '1 * * * *',
'args' => '(OPTIONAL) [Array or Hash]'
},
'My super iber cool job' => {
'class' => 'SecondClass',
'cron' => '*/5 * * * *'
}
}
Sidekiq::Cron::Job.load_from_hash hash
配列からより多くのジョブを読み込む:
array = [
{
'name' => 'name_of_job',
'class' => 'MyClass',
'cron' => '1 * * * *',
'args' => '(OPTIONAL) [Array or Hash]'
},
{
'name' => 'Cool Job for Second Class',
'class' => 'SecondClass',
'cron' => '*/5 * * * *'
}
]
Sidekiq::Cron::Job.load_from_array array
Bangサフィックスのメソッド(!)は、指定されたハッシュ/配列に存在しないジョブを削除し、同じ名前のジョブを更新し、名前が以前は不明であった場合に新しいジョブを作成します。
Sidekiq::Cron::Job.load_from_hash! hash
Sidekiq::Cron::Job.load_from_array! array
もしくはYAML(Resque-schedulerと同じ表記):
# config/schedule.yml
my_first_job:
cron: "*/5 * * * *"
class: "HardWorker"
queue: hard_worker
second_job:
cron: "*/30 * * * *" # execute at every 30 minutes
class: "HardWorker"
queue: hard_worker_long
args:
hard: "stuff"
# config/initializers/sidekiq.rb
schedule_file = "config/schedule.yml"
if File.exist?(schedule_file) && Sidekiq.server?
Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
end
バージョン3.xからは、スケジュールの個別の初期化子を使用せず、代わりにconfig.on(:startup)
をSidekiq構成に追加することをおすすめします。
Sidekiq.configure_server do |config|
config.on(:startup) do
schedule_file = "config/schedule.yml"
if File.exist?(schedule_file)
Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
end
end
end
または、ymlファイルsidekiq-cron-tasksからジョブをロードするために使用できます。これにより、rake task bundle exec rake sidekiq_cron: load
がrailsアプリケーションに追加されます。
Jobを見つける
# return array of all jobs
Sidekiq::Cron::Job.all
# return one job by its unique name - case sensitive
Sidekiq::Cron::Job.find "Job Name"
# return one job by its unique name - you can use hash with 'name' key
Sidekiq::Cron::Job.find name: "Job Name"
# if job can't be found nil is returned
Jobを削除する
# destroy all jobs
Sidekiq::Cron::Job.destroy_all!
# destroy job by its name
Sidekiq::Cron::Job.destroy "Job Name"
# destroy found job
Sidekiq::Cron::Job.find('Job name').destroy
Jobと連携する
job = Sidekiq::Cron::Job.find('Job name')
# disable cron scheduling
job.disable!
# enable cron scheduling
job.enable!
# get status of job:
job.status
# => enabled/disabled
# enqueue job right now!
job.enque!
スケジューリングの始め方
以下を実行してSidekiqワーカーを起動するのみです:
$ sidekiq
cronジョブのWebUI
SidekiqのWebUIを使用していてかつ、このWebUIにもcronジョブを追加する場合は、require 'sidekiq/web'
の後にrequire 'sidekiq/cron/web'
を追加します。
これにより、以下のようになります。

Forking ProcessesまたはNotImplementedError
問題
Unicornのようなforking Webサーバーを使用している場合、プロセスがフォークする前にRedis接続が使用されるという問題が発生し、次の例外が発生する可能性があります。
Redis::InheritedError: Tried to use a connection from a child process without reconnecting. You need to reconnect to Redis after forking.
これを回避するには、Sidekiq.configure_server
の呼び出しでジョブの作成をラップします。
Sidekiq.configure_server do |config|
config.on(:startup) do
schedule_file = "config/schedule.yml"
if File.exist?(schedule_file)
Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
end
end
end
内部
Sidekiqプロセスを開始すると、Sidekiq::Poller
インスタンスで1つのスレッドが開始され、キューへのスケジュールされたジョブの追加、再試行などが実行されます。
Sidekiq-Cronは、この開始プロシージャに自分自身を追加し、Sidekiq::Cron::Poller
で別のスレッドを開始します。Sidekiq::Cron:: Pollerは、有効なすべてのSidekiq cronジョブをキューに追加する必要がある場合、それらをチェックします(cronlineはチェック時間と一致します)。
Sidekiq-Cronは、デフォルトで30秒ごとにキューに入れられるジョブをチェックしています。次の設定で変更できます。
Sidekiq.options[:average_scheduled_poll_interval] = 10
Sidekiq-Cronは、複数のSidekiqプロセスまたはノードで安全に使用できます。これは、Redisでソートされたセットを使用して、要求した最初のプロセスのみがスケジュールされたジョブをキューにエンキューできることを決定します。
参考
今回は定期実行処理を行うgem「sidekiq-cron」のREADMEを読んでみたので、翻訳しつつその内容をまとめてみました。sidekiqに乗っかるだけでcronのような定期実行処理ができるので便利だなと個人的に感じました。