行動すれば次の現実

テック中心の個人ブログ

Sidekiqの引数とエラーリトライ時の挙動について

Sidekiqを使用するために知っておいてほしい、引数とエラーリトライ時の挙動についてまとめました。

引数のベストプラクティス

Sidekiqのperform_asyncの引数をJSON形式でRedisに永続化します。

例えばモデルオブジェクト自体を引数に渡してしまった場合、デフォルトではto_sにより<Quote:0x0000000006e57288>のようなオブジェクトハッシュ値として扱われるので、正しく復元できない場合があります。

仮に、to_sをカスタマイズして正しくJSONに復元出来たとしても、perform_asyncに渡す前と後でモデルオブジェクトの内容が変わった場合に意図しない挙動を招くことがあるので避けるべきです。引数としてはstring, integer, float, boolean, nil, array, hashの型でなければなりません。

# 悪い例
quote = Quote.find(quote_id)
SomeJob.perform_async(quote)

# 良い例
SomeJob.perform_async(quote_id)

リトライと冪等性

SidekiqではJobの冪等性(何度実行しても同じ結果になる)を意識して設計する必要があります。

Sidekiqはエラー時にリトライする機能が備わっています。エラーが発生した場合に、一定間隔おきに再施行(リトライ)を繰り返す仕組みになっています。

デフォルトでは25回リトライします。これを考慮して設計しなければ思わぬ不具合を引き起こす恐れがあります。

例えばクレジットカードの決済を実行してユーザーに課金処理を行い、完了メールを送るJobがあるとします。

課金処理が成功後に、完了メールを送信する処理でエラーが発生したとします。この場合リトライが発生して再度、課金処理が走ってしまうと二重課金が発生してしまいます。(デフォルトのリトライ回数だと最大で26回課金されてしまう)

このような場合を考慮して、どこまで処理が完了しているのかステータス管理するなどして、冪等性、完全性を保証させる必要があります。

もし冪等性の考慮が難しいなどの理由で意図的にリトライをさせたくない場合、以下のオプションでリトライを無効にすることが出来ます。

class SomeJob
  include Sidekiq::Job

  # リトライを無効にする
  sidekiq_options retry: 0
  # sidekiq_options retry: falseでも無効にできるが後述の違いがある

  def perform(*args)
    # Do something
  end
end

sidekiq_options retry: 0とretry: falseの違い

retry: 0 の場合

エラー時はデッド状態になり画面から再試行可能

retry: false の場合

エラー時は画面から再試行不可能

画面からの再試行もさせたくないなどの理由がなければretry: 0にしておくのが良いかと思います。

リトライ時の挙動について

例えばsidekiq_options retry: 2とした場合、以下のようにステータス管理されてJobがリトライされます。

  1. Jobが実行されてエラーが発生する
  2. 再試行(1回目)状態になり一定時間後にキューに登録される
  3. 1回目のリトライJobが実行されてエラーが発生する
  4. 再試行(2回目)状態になり一定時間後にキューに登録される
  5. 2回目のリトライJobが実行されてエラーが発生する
  6. デッド状態になる(画面から任意で再試行可能)

プロセスの再起動とジョブの再実行

retry: 0やretry: falseを指定しても、処理中にプロセスが再起動された場合、そのジョブは再起動後に再実行される可能性があります。これは、retryオプションがジョブの失敗時の再試行に関する設定であり、プロセスの再起動による中断とは異なるためです。

再起動後も含めてジョブを完全に再実行させたくない場合は、Sidekiq Proのユニークジョブ機能を使用するか、アプリケーション側で適切に制御する必要があります。

参考

https://github.com/mperham/sidekiq/wiki/Best-Practices