Ruby
Rails
ActiveJob

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