Edited at

ActiveJobでリトライ制御

More than 1 year has passed since last update.

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.

isaacseymour/activejob-retry


と書いてあり、まだまだ本番で使うには不安な感じです。

あと個人的には、少し重厚過ぎるな、という印象です。


やりたいこと

リトライ回数上限が設定できて、現在何回目の試行回数かわかって、リトライ時回数を超えたかどうかがわかれば最低限事足りそうです。

それができれば、以下のようにリトライ回数制御を追加可能です。

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

これで良さそうなので、これを動くように実装していきます。


実装

リトライ回数の受け渡しについてですが、公式ドキュメントにほぼそのままのサンプルが載っています。serializedeserializeをオーバーライドすることにより、シリアライズ可能なインスタンス変数の持ち回しが実現できそうです。

というわけで、公式ドキュメントを参考に実装してみました。

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 しか使ってないので、壊れにくいかなーと思います。

あと、メソッドを少し提供しているだけなので、制御の自由度が高いです。


デメリット

逆に言うと、かなりシンプルなので、色々自分で実装する必要があります。

自動でリトライを行ってくれる、というモジュールではありません。


元記事





  1. enqueueというのもありますが、retry_jobと全く同じ挙動に見えます。ref: ActiveJob::Enqueuing