ActiveJob はまだまだリトライ制御機能が弱く、retry_job
での単純な再実行位しか機能がありません1。
そこで、公式ドキュメントで公開されている API のみを活用し、ある程度の制御が行えるモジュールを作成しました。
成功するまでリトライ
まず retry_job
を使ったシンプルな例です。
class RetryJob < ApplicationJob
queue_as :default
rescue_from(StandardError) do
retry_job(wait: 5.minutes)
end
def perform(*args)
# Do something later
end
end
StandardError が発生すると、rescue_from
で補足され、5分後に再実行するようにスケジューリングされます。
ただし、これでは成功するまで無限にリトライが行われてしまいます。リトライ回数の制限を行おうと思っても、現状 ActiveJob ではその制御機構がありません。
当然 Gems もいくつか既に提供されていますが、まだまだ「これが間違いない」と思える gem が出てきてないように思います。
おそらく一番 Github でスターが多いのが ActiveJob::Retry なんですが、
This is an alpha library in active development, so the API may change.
と書いてあり、まだまだ本番で使うには不安な感じです。
あと個人的には、少し重厚過ぎるな、という印象です。
やりたいこと
リトライ回数上限が設定できて、現在何回目の試行回数かわかって、リトライ時回数を超えたかどうかがわかれば最低限事足りそうです。
それができれば、以下のようにリトライ回数制御を追加可能です。
class LimitedRetryJob < ApplicationJob
queue_as :default
retry_limit 5
rescue_from(StandardError) do |exception|
raise exception if retry_limit_exceeded?
retry_job(wait: attempt_number**2)
end
def perform(*args)
# Do something later
end
end
これで良さそうなので、これを動くように実装していきます。
実装
リトライ回数の受け渡しについてですが、公式ドキュメントにほぼそのままのサンプルが載っています。serialize
とdeserialize
をオーバーライドすることにより、シリアライズ可能なインスタンス変数の持ち回しが実現できそうです。
というわけで、公式ドキュメントを参考に実装してみました。
class ApplicationJob < ActiveJob::Base
DEFAULT_RETRY_LIMIT = 5
attr_reader :attempt_number
class << self
def retry_limit(retry_limit)
@retry_limit = retry_limit
end
def load_retry_limit
@retry_limit || DEFAULT_RETRY_LIMIT
end
end
def serialize
super.merge("attempt_number" => (@attempt_number || 0) + 1)
end
def deserialize(job_data)
super
@attempt_number = job_data["attempt_number"]
end
private
def retry_limit
self.class.load_retry_limit
end
def retry_limit_exceeded?
@attempt_number > retry_limit
end
end
それぞれ、
- ApplicationJob.retry_limit
- リトライ上限設定
- ApplicationJob#attempt_number
- 現在の試行回数を取得
- ApplicationJob#retry_limit_exceeded?
- リトライ上限を超えてるかどうか確認
という機能を提供しています。
本番環境で使う
全サブクラスでリトライ制御が必要になるわけではないので、モジュール化を行いました。
メリット
公式ドキュメントに載ってる API しか使ってないので、壊れにくいかなーと思います。
あと、メソッドを少し提供しているだけなので、制御の自由度が高いです。
デメリット
逆に言うと、かなりシンプルなので、色々自分で実装する必要があります。
自動でリトライを行ってくれる、というモジュールではありません。
元記事
-
enqueue
というのもありますが、retry_job
と全く同じ挙動に見えます。ref: ActiveJob::Enqueuing ↩