Rails

【Rails】定期実行処理を行うgem「sidekiq-cron」のREADMEを読んでみる

Rails

 

今回は定期実行処理を行うgem「sidekiq-cron」のREADMEを読んでみたので、翻訳しつつその内容を備忘録としてまとめました。

このREADMEの内容は2022/06/12時点のものになります。

Sidekiq-Cron

Sidekiqのスケジューリング拡張機能

🎬Sidekiq-Cronの紹介ビデオ

 

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"
Railsを使用していない場合はrequire 'sidekiq'の後にrequire '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'を追加します。

これにより、以下のようになります。

sidekiq cron web ui

 

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
このAPIはSidekiq3.xでのみ使用できます。

内部

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のような定期実行処理ができるので便利だなと個人的に感じました。