背景
非同期処理
一般的なブラウザは、送信したHTTPリクエストが30秒以内に応答されない時はタイムアウトし、そのHTTPリクエストは失敗したと判定します。Webアプリケーションが長時間かかる処理を行いたい場合は、ブラウザがタイムアウトしないように、HTTPリクエストに応答を一旦返し、その後実際の処理を実行します。このような処理をリクエストの応答と実処理の完了が同期していない(同時でない)点から、非同期処理と呼びます。
非同期処理フレームワーク
非同期処理では、一つ一つの処理をジョブと呼びます。Webアプリケーションは、HTTPリクエストを受け取ると、次のように動作します。
- リクエストに応じたジョブを作成
- ジョブをキューと呼ばれる領域に保存
- HTTPリクエストに応答を返す
非同期処理にはワーカーという実行プログラムがあります。ワーカーは、キューを監視し、キューにジョブが保存されたら、キューからジョブを取り出し、そのジョブを実行します。
ワーカーの処理が終わる前に新しいリクエストが来た場合、Webアプリケーションは新しいジョブをキューに保存します。キューには一定数のジョブが保存でき、ワーカーは実行中のジョブが完了したら、次のジョブがないかキューを確認します。キューが存在するおかげでWebアプリケーションは、ワーカーの実行状態を気にせずにジョブを作成できます。
一般にキューに入れられたジョブは先入れ先出しで処理されます。このためキューはキューと呼ばれるます。
以下の機能をまとめて提供するプログラムを非同期処理フレームワークと呼びます。
- ジョブを定義するフォーマット
- キューの実装
- ワーカーの実装
- キューとワーカーを管理する機能
ActiveJobとSidekiq
Ruby on RailsというWebアプリケーションフレームワークには、非同期処理を実行するためにActiveJobという機能があります。ActiveJobでは、処理を実行する非同期処理フレームワークをいくつかの候補から選択することが出来ます。その1つにSidekiqがあります。
SidekiqはRubyで書かれた非同期処理フレームワークです。Ruby on Railsと関わりなく広く使われています1。Sidekiq自身には豊富な機能がありますが、ActiveJobから利用する場合にはいくつかの制限があります。その1つにsidekiq_options
メソッドを使った、ジョブ単位のオプション設定があります。
ジョブ単位のオプション設定
Sidekiqを、ActiveJobを経由せずに、直接使う時は、Sidekiq::Workerというクラスを継承してジョブを実装します。Sidekiq::Workerにはsidekiq_options
メソッドがあります。sidekiq_options
を使うとジョブを登録するキューや、ジョブが失敗したときのリトライ処理の有無が設定できます。次の4項目を設定できます。
- queue
- retry
- backtrace
- pool
詳しくはAdvanced Options · mperham/sidekiq Wikiを読んでください。
命題
ActiveJobからSidekiqを使う際に、ジョブにsidekiq_optionsを設定できるでしょうか?
結論
Sidekiq 6.0.1 + Rails 6.0.2の組合せで、できるようになるらしい。
Sidekiq 6.0.1
As of Sidekiq 6.0.1 you can sidekiq_options in your ActiveJobs and configure the standard Sidekiq retry mechanism.
翻訳すると...
Sidekiq 6.0.1以降、ActiveJobsでsidekiq_optionsを使用して、標準のSidekiq再試行メカニズムを構成できます。
Rails 6.0.2
Oh and you need Rails master too, it needs Rails 6.0.1.
翻訳すると...
ああ、Railsのmasterブランチも必要です。Rails6.0.1が必要です。
その後、該当コミットがRails 6.0.1に入らなかったため、6.0.2が必要になりました。
Update: support code will only make it to Rails 6.0.2
その他の悪あがき
現時点で、ActiveJobからsidekiq_optionsは設定できませんが、ジョブ単位にいくつかの振る舞いを変更することが出来ます。
queue
ActiveJobからqueue_as
メソッドを使えばジョブを登録するキューを指定することが出来ます。Sidekiqではキュー毎にジョブのチェック頻度が設定できます。これを使ってジョブ単位の優先順位を設定できます。
詳しくはActive Job の基礎 - Rails ガイドやAdvanced Options · mperham/sidekiq Wikiを読んでください。
retry
リトライ回数を増やす
Sidekiqではリトライ回数を設定することが出来ます。ジョブが失敗した時に何回リトライするかを設定します。ジョブ単位の設定ではありませ。すべてのジョブで共通の設定です。
詳しくはError Handling · mperham/sidekiq Wikiを読んでください。
Sidekiqでのリトライ回数に、ActiveJobでのリトライ回数を追加できます。
Active Job · mperham/sidekiq Wiki によると
The default AJ retry scheme is 3 retries, 5 seconds apart. Once this is done (after 15-30 seconds), AJ will kick the job back to Sidekiq, where Sidekiq's retries with exponential backoff will take over.
翻訳すると...
「デフォルトのActiveJob再試行スキームは、5秒間隔で3回再試行されます。これが完了すると(15〜30秒後)、ActiveJobはSidekiqにジョブをキックバックします。Sidekiqは指数関数的なバックオフを使用した再試行を引き継ぎます。」
ActiveJobではretry_onメソッドを使ってリトライ回数を設定できます。例えば、次のジョブは3回リトライします。2
class AlwaysFailJob < ApplicationJob
retry_on ZeroDivisionError, attempts: 3
def perform(params)
p "run job !!!"
1/0
end
end
このジョブを実行すると、ActiveJobが3回リトライしたあとで、Sidekiqは既定回数リトライをします。
つまり、ActiveJobがリトライする回数分、リトライ数を増やせます。
リトライ回数を0にする
ActiveJobはdicard_onメソッドを使って、特定の例外が起きた時に、ジョブを成功したことに出来ます。例えば、次のジョブは実行するとZeroDivisionErrorが起きますが、成功します。
class AlwaysSuccessJob < ApplicationJob
discard_on ZeroDivisionError
def perform(params)
p "run job !!!"
1/0
end
end
ジョブは成功するので、Sidekiqはリトライしません。
backtrace
ジョブの失敗時に例外のbacktrace(Ruby以外の言語ではスタックトレースとも呼ばれます)を保存して、Sidekiqの管理UIから参照するためオプションです。このオプションはActiveJobから設定出来ません。
pool
SidekiqはRedisというインメモリデータベースをつかって、ジョブを保存するキューを実装しています。
ジョブを登録するときに使用するRedisへのコネクションプールを指定するオプションです。このオプションはActiveJobから設定出来ません。
このオプションの使い道は知りません。
Sidekiq::Workerを使う
つまりActiveJobを使うのをやめます。
RSpecでテストを書く時にActiveJob::TestHelperが使えません。3
代わりにSidekiq::Testingを使います。例えば、次のようにテストを書きます。
require 'rails_helper'
require 'sidekiq/testing'
Sidekiq::Testing.fake!
describe ExampleJob do
it do
expect {
ExampleJob.perform_async nil
}.to change(ExampleJob.jobs, :size).by(1)
end
end
詳しくはTesting · mperham/sidekiq Wikiを読んでください。
参考
-
たとえばRubyGems.orgからは6000万回ダウンロードされています。 ↩
-
retry_onはRails 5.1.0で追加された機能です。それ以前のバージョンのActiveJobを使う場合は自分で実装する必要があります。ActiveJobでリトライ制御 - Qiitaなどを参考にしてください。 ↩
-
ActiveJob::TestHelperの使用例が知りたい方はRSpec でキューイングした ActiveJob を同期実行する - QiitaやDelayed Job Queue Adapter in RSpec with Rails 5.1 - Today I Learnedを読んでください。 ↩