SidekiqをActive Jobから利用する
最終更新 2019/09/18 編集者 Mike Perham
Rails 4.2からActive Jobが導入されました。Active Jobはジョブキューシステムのための標準的なインターフェースです。
Active JobはSidekiqと一緒に利用することができます。
Active Jobのセットアップ
Active Jobのアダプターとして :sidekiq
を指定する必要があります。そうしないとデフォルトの :async
という値になってしまいます。
この設定は config/application.rb
に記載してください。
class Application < Rails::Application
# ...
config.active_job.queue_adapter = :sidekiq
end
新しいジョブの生成にはRailsのジェネレーター機能を使うことができます。
rails generate job Example
上記のコマンドを実行すると app/jobs/example_job.rb
というファイルが作成されます。
class ExampleJob < ActiveJob::Base
# defaultという名前のキューを利用する
queue_as :default
def perform(*args)
# ジョブの中身
end
end
使用方法
Sidekiqを単体で使う場合のジョブと同じように、どこからでもキューにジョブをプッシュすることができます。
ExampleJob.perform_later args
上記のコードを実行後すぐに、Sidekiqはジョブを実行します。何らかの理由でジョブが失敗した場合、Sidekiqは通常どおり再試行します。
エラーハンドリングをカスタマイズする
Active Jobは、Sidekiqの再試行機能を完全にはサポートしていません。
代わりに、特定の例外が発生したときに再試行するための抽象化された仕組みがあります。
class ExampleJob < ActiveJob::Base
rescue_from(ErrorLoadingSite) do
retry_job wait: 5.minutes, queue: :low_priority
end
def perform(*args)
# Perform Job
end
end
デフォルトのActive Jobの再試行方法は5秒間隔で3回です。
これが完了すると(15〜30秒後)、Active JobはSidekiqにジョブを戻します。Sidekiqはエクスポーネンシャルバックオフを使用して再試行を引き継ぎます。
Sidekiq 6.0.1以降では、ActiveJobsから sidekiq_options
を使用することで、標準のSidekiq再試行メカニズムを利用できます。
class ExampleJob < ActiveJob::Base
sidekiq_options retry: 5
def perform(*args)
# Perform Job
end
end
Action Mailer
Action Mailerには、#deliver_later
という非同期でメールを送信するためのメソッドが付属しています。(メールはバックグラウンドジョブで送信されます)。
Active JobがSidekiqを使用するように設定されている場合に限り、#deliver_later
を使用できます。
Sidekiqだけを使用する場合とは異なり、Active Jobを使用すると、ActiveRecordインスタンスがグローバルIDでシリアライズされます。シリアライズされたレコードは後でデシリアライズされます。
メール送信ジョブは mailers というキューにプッシュされます。Sidekiqを起動する際にこのキューを指定することを忘れないようにしてください。
bundle exec sidekiq -q default -q mailers
メール送信ジョブをキューに送る基本的なコードは下記の通りです。
UserMailer.welcome_email(@user).deliver_later
ジョブキューに送らずに同期的にメールを送信するコードは下記の通りです。
UserMailer.welcome_email(@user).deliver_now
Sidekiqでは、遅延を設定してメールを送信するオプションがありました。Active Jobでも同様に遅延を設定することができます。
Sidekiqだけで遅延を設定するコードは下記の通りです。
UserMailer.delay_for(1.hour).welcome_email(@user.id)
UserMailer.delay_until(5.days.from_now).welcome_email(@user.id)
Active Job経由で同様のことを行うコードは下記の通りです。
UserMailer.welcome_email(@user).deliver_later(wait: 1.hour)
UserMailer.welcome_email(@user).deliver_later(wait_until: 5.days.from_now)
グローバルIDを使用する
RailsのグローバルID機能を使うことで、ActiveRecordオブジェクトを #perform
の引数として渡す際にシリアライズすることができます。そのため、
def perform(user_id)
user = User.find(user_id)
user.send_welcome_email!
end
上記のコードは、下記のコードで置き換えることができます。
def perform(user)
user.send_welcome_email!
end
残念なことに、上記のコードの違いによって、Active Jobを使う場合とそうでない場合で例外処理に違いが生じています。
ジョブがエンキューされる前までは User
モデルのレコードが存在し、perform
が実行される前にそのレコードが削除された場合、通常Sidekiqでは下記のように場合分けして処理します。
def perform(user_id)
user = User.find_by(id: user_id)
if user
user.send_welcome_email!
else
# user レコードが削除されている場合の処理
end
end
それがActive Jobを使用する場合は、User
のインスタンスをデシリアライズする際に perform(user)
がレコードが存在しないことに対する例外を raise
するようになります。
その場合の例外処理は下記の通りです。
class MyJob < ActiveJob::Base
rescue_from ActiveJob::DeserializationError do |exception|
# user レコードが削除されている場合の処理
end
# ...
end
ジョブID
ActiveJobには、Sidekiqにとって意味のない独自のジョブIDがあります。 SidekiqのJIDを取得するには provider_job_id
を使用します。
job = SomeJob.perform_later
jid = job.provider_job_id
パフォーマンス
ベンチマークによると、ActiveJobを使うとRedisへのジョブのプッシュがおよそ2〜20倍遅くなります。そして、ジョブを処理する際のオーバーヘッドが3倍ほどあります。(Rails 5.1.4 と Sidekiq 5.1.1 の場合)
この件については このイシュー のコメントを読んでください。
キュー名のプリフィックス
Active Jobでは、キュー名にプレフィックスを設定できます。 環境固有のプレフィックスを使用しないでください。
各環境では完全に別れた個別のRedisデータベースを使用する必要があります。
そうしないと、すべての環境で同じRetryセットとScheduledセットが共有され、混乱が生じる恐れがあります。
有償版のSidekiqとの併用
Sidekiq ProおよびSidekiq Enterpriseの機能の一部は、ActiveJobと一緒に使用しようとすると、予測しない壊れ方をする可能性があります。
たとえば、バッチ内でのActiveJobsの使用は基本的なユースケースでは機能しますが、ActiveJobの再試行メカニズムと併用しようとすると失敗します。
何か疑わしい挙動が起きた場合は、Active JobとSidekiqのネイティブなAPIを併用しないようにしてください。