Shoryuken(しょうりゅうけん)は、Amazon SQSのメッセージをマルチスレッドで処理するRuby製のワーカーgemです。格闘ゲームの技名と同じ綴りのため検索結果が混ざりますが、ここで扱うのはRubyのgemのほうです。Redisを前提とするSidekiqと違い、キューの実体はSQSそのもの。ブローカーを自前で持たずにジョブ基盤を組める点が採用の決め手になります。
2026年1月19日にメジャーバージョン7.0.0が公開され、動作要件とエラー処理が大きく変わりました。本稿は2026年8月18日時点の最新版7.0.3を実際に導入し、設定・Active Job連携・FIFO・リトライの挙動を実行して確かめた結果をまとめています。特にFIFOキューでジョブが黙って消える重複排除IDの扱いは、公式READMEにも記載がなく事故になりやすい箇所です。
まとめ
Shoryuken 7.0.3を導入・実行して確認できた要点は次のとおりです。
- 7.0系の動作要件はRuby 3.2以上・Rails 7.2以上。gemspecの
required_ruby_versionは>= 3.2.0で、READMEの「Ruby 3.0 or greater」という記述は7.0.0の変更に追随していません。 - ジョブの多重度は
concurrencyで指定し、既定値は25スレッドです。キューごとに配分を変えるには重み付き指定を使います。 - Active Job拡張は
Shoryuken.active_job?という読み込み順ゲートの内側で初期化されます。順序が崩れると設定不備ではなくNoMethodErrorという無関係な顔をした例外で落ちます。 - FIFOキューでは既定で内容ベースの重複排除IDが自動生成され、同じジョブクラスと同じ引数の投入は5分間で1件に潰れます。7.0.3で追加された設定でこの生成を止められます。
- Shoryuken独自ワーカーの
retry_intervalsは再試行を打ち切りません。配列を使い切ると最後の値が延々と繰り返されます。打ち切りはSQS側のリドライブポリシーで行います。
以下、それぞれの根拠と実際の設定手順を順に見ていきます。
SQS専用ワーカーとしての立ち位置とSidekiqからの選び分け
Shoryukenが担うのは「SQSからメッセージを取り出し、スレッドプールでワーカーに渡し、成功したら削除する」というループだけです。キューの永続化・可視性制御・順序保証はすべてSQS側の機能で、gemはその消費者に徹しています。この分担が、他のジョブ基盤との違いをそのまま決めます。
Sidekiq・Solid Queueとの分担基準
| 観点 | Shoryuken | Sidekiq | Solid Queue |
|---|---|---|---|
| キューの実体 | Amazon SQS | Redis | RDB |
| 運用対象 | ワーカープロセスのみ | Redisとワーカー | DBとワーカー |
| 順序保証 | FIFOキューで可 | なし | なし |
| 再試行の主体 | SQSの可視性タイムアウト | gem内部の再試行 | gem内部の再試行 |
| 管理画面 | なし(CloudWatch) | Web UI同梱 | Mission Control |
選定の分かれ目は再試行の主体です。Sidekiqは失敗ジョブを自前のRedis上に積み直しますが、Shoryukenは失敗メッセージをSQSへ戻すだけで、何回で諦めるかの判断もSQSに委ねます。ジョブの信頼性設計がコードではなくキューの属性設定になる、ということです。すでにAWS上で動いていてワーカープロセス以外を増やしたくないなら有力な選択肢になります。逆に管理画面でジョブを目視したい、失敗ジョブを画面から再実行したいという運用要件が強いなら、Sidekiqとは?Rubyで非同期処理を簡単に実現するバックグラウンドジョブツールで扱っている構成のほうが手数は少なくなります。
採用前に確認したい開発の継続状況
SQSという狭い用途のgemなので、保守が続いているかは採用判断に直結します。2026年8月18日時点でリポジトリruby-shoryuken/shoryukenはアーカイブされておらず、直近のコミットは2026年8月6日、スター数は2,125、RubyGems上の累計ダウンロードは約3,280万件です。6.2.1が2024年2月で止まったあと1年3か月ほどリリースが途絶えましたが、2025年5月のアルファ版を経て2026年1月に7.0.0が出て、以降は7.0.1・7.0.2・7.0.3と数か月おきに続いています。空白期間だけを見て開発停止と判断するのは誤りです。
7.0系の動作要件とREADMEの記述ずれ
7.0.0はサポート範囲を切り上げるメジャーアップデートでした。導入前に確認すべき下限を先に押さえます。
| 項目 | 6.2.1(2024-02-09) | 7.0.3(2026-07-10) |
|---|---|---|
| gemspecのRuby下限 | 指定なし | 3.2.0以上 |
| 対応Rails | 7.1以前を含む | 7.2・8.0・8.1 |
| SQS依存 | aws-sdk-core 2以上 | aws-sdk-sqs 1.66.0以上 |
| 読み込み方式 | 個別require | zeitwerk 2.6系 |
値はいずれも各版のgemspecを読んだ実測です。6.2.1にはRubyの下限指定そのものが無く、依存もaws-sdk-coreでした。注意したいのは、7.0系の下限がREADMEに反映されていない点です。GitHubのREADMEには2026年8月18日時点でも「Ruby 3.0 or greater」と書かれていますが、実際のrequired_ruby_versionは>= 3.2.0で、CHANGELOGの7.0.0にも「Drop support for Ruby 3.1」と明記されています。READMEを根拠にRuby 3.1のまま上げようとすると、bundle installの段階で解決に失敗します。
$ gem install shoryuken -v 7.0.3
Successfully installed shoryuken-7.0.3
$ ruby -e "s = Gem::Specification.find_by_name('shoryuken','7.0.3'); puts s.required_ruby_version"
>= 3.2.0
Ruby 3.1系を使い続ける必要がある場合、7.0系へは上げられません。CHANGELOGも6.x系に留まるよう案内しています。
shoryuken.ymlの構成とジョブの多重度の決め方
設定はconfig/shoryuken.ymlに集約し、起動時に読み込ませます。ここで指定するconcurrencyが、1プロセスが同時に処理するジョブの多重度です。
既定値25が意味するスレッド数
何も書かなければ既定値が使われます。実際にロードして確認した値が次のものです。
require 'shoryuken'
pp Shoryuken.options.slice(:concurrency, :timeout, :thread_priority, :delay)
# => {concurrency: 25, timeout: 8, thread_priority: -1, delay: 0.0}
concurrency: 25は、そのプロセスが最大25件のメッセージを並行して処理するという意味です。実際の上限を決めるのはこの数値ではなく下流の資源で、ジョブがDBへ書き込む処理ならコネクションプールが先に頭打ちになります。RailsのプールサイズがShoryukenの多重度より小さいと、超過したスレッドがコネクション待ちで滞留し、その間もメッセージの可視性タイムアウトは進みます。多重度を上げるときはプールサイズを同数以上に合わせてください。
課金面も考慮が要ります。SQSはメッセージ単位ではなくAPIリクエスト単位の課金で、AWSは「すべてのAmazon SQSアクションが1リクエストとして数えられる」と説明しています。1リクエストで最大10件まで受信できるため、多重度だけを上げて空きスレッドを増やすと、受信リクエストの回数ばかりが伸びます。次のポーリング設定と合わせて調整してください。
timeout: 8はジョブの実行時間制限ではなく、シャットダウン時に実行中のワーカーを待つ秒数です。長時間ジョブを扱う場合、この値を延ばさないと停止時に強制終了されます。
重み付きキューによる優先度配分
複数キューを扱うときは、キュー名に重みを添えて記述します。
# config/shoryuken.yml
concurrency: 25
delay: 25
queues:
- [high_priority, 6]
- [default, 2]
- [low_priority, 1]
この設定を読み込ませると、キュー定義は名前と重みの組として解釈されます。
require 'erb'
require 'shoryuken'
require 'shoryuken/environment_loader'
loader = Shoryuken::EnvironmentLoader.new(config_file: 'shoryuken.yml')
loader.send(:initialize_options)
pp Shoryuken.options.slice(:concurrency, :delay, :queues)
# => {concurrency: 25,
# delay: 25,
# queues: [["high_priority", 6], ["default", 2], ["low_priority", 1]]}
ここで誤解されやすいのが重みの効き方です。指定した数値は「常にその比率で読む」という意味ではなく、ローテーションに確保できる枠の上限値です。既定のWeightedRoundRobin戦略を直接動かすと、起動直後は重みに関係なく全キューが1枠ずつの均等巡回から始まります。
# 起動直後のローテーション(まだメッセージが見つかっていない)
high_priority -> default -> low_priority -> high_priority -> default -> low_priority
# high_priority でメッセージが見つかるたびに枠が1つずつ増える
INFO: Increasing high_priority weight to 2, max: 6
INFO: Increasing high_priority weight to 3, max: 6
...
INFO: Increasing high_priority weight to 6, max: 6
# 上限に達した後のローテーション
high_priority -> default -> low_priority -> high_priority -> high_priority
-> high_priority -> high_priority -> high_priority -> high_priority -> default
つまり重みは、混んでいるキューへ処理を寄せていくための上限であって、初期状態の配分ではありません。逆にメッセージが見つからないキューは一時的にポーリング対象から外れます。散発的にしかジョブが入らないキューでは重みを大きくしても効果が出にくく、常時流量のあるキューでこそ差が出る仕組みです。delayは、メッセージが見つからなかったキューをポーリング対象から外しておく秒数です。既定の0のままだと空振りのReceiveMessage呼び出しが積み上がり、リクエスト単位の課金がそのまま増えます。ジョブが散発的にしか入らないキューでは25秒程度を明示しておくと無駄が減ります。なお設定ファイルはERBとして評価されるため、環境変数の埋め込みが可能です。
Active Job連携で初期化が沈黙する読み込み順の罠
Shoryuken独自のワーカークラスを書く方法もありますが、RailsアプリではRails Active Jobとは?非同期ジョブ処理の実装とSolid Queue・リトライ設定で解説しているActive Job経由が標準です。アダプタを差し替えるだけで既存のジョブクラスをそのまま流用できます。
アダプタ指定とキュー名の対応
# config/application.rb
config.active_job.queue_adapter = :shoryuken
queue_asに書いたキュー名が、そのままSQSのキュー名として解決されます。queue_as :defaultならSQS上にもdefaultという名前のキューが必要です。存在しない場合はShoryuken::Errors::QueueNotFoundErrorが上がります。7.0.0でエラークラスが専用の名前空間に整理され、以前はRuntimeErrorやArgumentErrorで区別できなかった失敗を型で捕まえられるようになりました。
読み込み順が崩れたときに出る2つの例外
これが7.0系で最も分かりにくい詰まり方です。Shoryukenは、gem本体を読み込んだ時点でActive Jobが存在するかどうかを見て、Active Job拡張を読むかどうかを決めています。
# shoryuken.rb の末尾(7.0.3)
if Shoryuken.active_job?
require 'active_job/extensions'
require 'active_job/queue_adapters/shoryuken_adapter'
require 'active_job/queue_adapters/shoryuken_concurrent_send_adapter'
end
この判定はshoryukenを読み込んだ瞬間に1回だけ行われます。Active Jobより先にShoryukenが読み込まれると分岐が偽になり、拡張もアダプタも定義されません。あとからActive Jobを読んでも手遅れです。
require 'shoryuken'
Shoryuken.active_job? # => nil ← この時点の判定で拡張を読むかが決まる
require 'active_job'
Shoryuken.active_job? # => "constant"(後から読んでも判定はやり直されない)
ActiveJob::Base.method_defined?(:sqs_send_message_parameters) # => false
ActiveJob::QueueAdapters.const_defined?(:ShoryukenAdapter) # => false
ここで紛らわしいのは、Shoryuken.active_job?自体は呼び出した時点でActive Jobの有無を評価するため、順序を誤った後に確認すると"constant"を返してしまう点です。読み込みが済んでいるかどうかはmethod_defined?側で判断してください。
この状態でアダプタを指定すると、ActiveJob::Base.queue_adapter = :shoryukenの行でNameError: uninitialized constant ActiveJob::QueueAdapters::ShoryukenAdapterが上がります。アダプタ定数だけを個別にrequireしていてactive_job/extensionsが読まれていない場合は、ジョブ投入まで進んでからNoMethodError: undefined method 'sqs_send_message_parameters'で落ちます。どちらの文面もキュー設定にもAWS認証にも触れないため、設定ファイルやIAMポリシーを疑って時間を溶かしがちです。Railsアプリ内ではフレームワークが先にActive Jobを読むため通常は起きませんが、rakeタスクやスタンドアロンのワーカースクリプト、初期化子で個別にrequireしている構成では順序が崩れます。この2つの例外を見たら、まず読み込み順を疑ってください。
perform_all_laterによる一括送信
7.0.0でRails 7.1以降の一括投入に対応しました。ActiveJob.perform_all_laterに複数ジョブを渡すと、SQSのSendMessageBatchにまとめられます。
ActiveJob.perform_all_later([ReportJob.new(1), ReportJob.new(2), ReportJob.new(3)])
# 実測: send_message_batch の呼び出し 1回、entries 3件
SQSのバッチ上限に合わせ、キューごとに10件ずつ分割される仕組みです。ジョブを100件投入するなら、個別に呼べば100リクエストのところが10リクエストで済み、リクエスト課金と往復遅延の両方が減ります。
あわせて7.0.0では、Rails 8.1のActive Job Continuationsにも対応しました。アダプタがstopping?を実装し、デプロイなどでワーカーが停止に入ったことをジョブ側へ伝えられます。長時間ジョブを途中で打ち切らずに済ませる設計はActive Job Continuationsとは何か?長時間ジョブを中断・再開可能にする新機能の基本概念で扱っています。
FIFOキューでジョブが消える原因は重複排除IDの二層構造
ここが最も事故になりやすく、かつ公式ドキュメントに説明がない部分です。FIFOキューを使うと、同じジョブを2回投入したのに1回しか実行されないという現象が起きます。バグではなく、重複排除IDが二か所で生成されている結果です。
既定で付く内容ベースの重複排除ID
Active JobアダプタはFIFOキュー宛のメッセージに対し、シリアライズしたジョブ本体からjob_idとenqueued_atを除いた内容のSHA-256を重複排除IDとして付けます。同じジョブクラスに同じ引数を渡せば、投入のたびに同じIDになるという意味です。実際に同一引数で2回、引数を変えて1回投入した結果が次のものです。
FifoJob.perform_later(1) # dedup_id = ec89062088c630dd...
FifoJob.perform_later(1) # dedup_id = ec89062088c630dd... ← 完全に一致
FifoJob.perform_later(2) # dedup_id = ec910290bcbaadbb... ← 引数が違えば別ID
message_group_id = "ShoryukenMessage" # 全ジョブ共通の既定値
AWSの仕様では、同じ重複排除IDのメッセージは5分間の重複排除インターバル内で1件に潰されます。公式ドキュメントは「5分の重複排除インターバル内でSendMessageを再試行しても重複は発生しない」と説明しており、これは再送対策としては正しい挙動です。しかし「同じ利用者に同じ通知を5分以内に2回送る」「同じレポートIDで再集計をかける」といった、意図的に同じ引数で2回実行したいジョブでは、2件目が例外もログも出さずに消えます。送信元は成功したように見えるため、発覚が遅れます。
またmessage_group_idが全ジョブ共通の固定値である点も見落とされがちです。FIFOキューはグループ単位で順序を保証し、グループ内は直列実行になります。既定のままだとすべてのジョブが1グループに入るため、多重度を25に上げてもFIFOキューの処理は1件ずつしか進みません。並行性が必要なら、テナントIDなど分割可能な値をグループIDに指定する必要があります。
7.0.3で追加されたオプトアウトと挙動の差
2026年7月10日公開の7.0.3で、この自動生成を止める設定が入りました。
# config/initializers/shoryuken.rb
Shoryuken.active_job_fifo_message_deduplication = false
ここで一点、読み取り側のアクセサ名に注意が必要です。CHANGELOGは設定名を疑問符なしで書いていますが、値を読むメソッドは疑問符付きだけが定義されています。
Shoryuken.active_job_fifo_message_deduplication? # => true(既定値)
Shoryuken.active_job_fifo_message_deduplication # => NoMethodError
そして重要なのは、この設定をfalseにしても重複排除IDが消えるわけではないという点です。Active Jobアダプタが付けるのをやめると、今度はキュー層のフォールバックが働き、メッセージ本文全体のSHA-256が使われます。本文には毎回異なるjob_idとenqueued_atが含まれるため、結果として投入ごとにユニークなIDになります。同一引数で2回投入した実測値が次のものです。
# 既定(true): 同一引数の2回投入
ec89062088c6... と ec89062088c6... 一致 => 2件目がSQSで消える
# false に設定: 同一引数の2回投入(値は投入ごとに変わる)
96ec98facffd... と ec218849a2ac... 不一致 => 2件とも配送される
判断基準は単純です。同じ内容の再送を1回に丸めたいなら既定のまま、同じ内容でも投入回数だけ実行してほしいならfalseにします。ジョブが冪等に書かれていないのに既定のままだと、業務上必要な2回目が静かに落ちます。
FIFOキューにwaitを付けたときの例外
FIFOキューはメッセージ単位の遅延配信に対応していません。Active Jobのset(wait:)をFIFOキュー宛に使うと、7.0.1以降は専用の例外で止まります。
FifoJob.set(wait: 3).perform_later(1)
# => Shoryuken::Errors::FifoDelayNotSupportedError:
# FIFO queue 'orders.fifo' does not support per-message delays.
# When using ActiveJob retry_on with FIFO queues, set `wait: 0`.
6.x系ではAWS側の分かりにくいエラーがそのまま出ていた箇所です。実務で問題になるのはretry_onとの組み合わせで、retry_onは既定で3秒待つため、FIFOキューを使うジョブに何気なく付けると再試行のたびにこの例外に当たります。FIFOキューではwait: 0を明示してください。
版によって例外の型が違う点にも注意が必要です。7.0.0はこの状況でArgumentErrorを送出していましたが、7.0.1で専用のFifoDelayNotSupportedErrorに置き換わりました。後者はShoryuken::Errors::BaseErrorの子でArgumentErrorを継承していないため、7.0.0時点で書いたrescue ArgumentErrorは7.0.1以降で素通りします。
retry_intervalsは再試行を打ち切らない
Shoryuken独自ワーカーではretry_intervalsで再試行間隔を指定できます。指数バックオフの設定として紹介されることが多いのですが、この配列は「何回まで試すか」を表していません。
配列を使い切った後の挙動
間隔を決める処理を実際に呼び出し、失敗回数ごとの戻り値を並べた結果が次のものです。
class MyWorker
include Shoryuken::Worker
shoryuken_options queue: 'default', auto_delete: true,
retry_intervals: [1, 5, 25, 125]
end
# 失敗回数 -> 次に設定される可視性タイムアウト
# 1回目 -> 1秒 5回目 -> 125秒
# 2回目 -> 5秒 6回目 -> 125秒
# 3回目 -> 25秒 7回目 -> 125秒
# 4回目 -> 125秒 8回目 -> 125秒
4回目で配列を使い切った後も、最後の値である125秒が繰り返し使われ続けます。再試行が止まることはありません。7.0.3のCHANGELOGでも、この点について「最後の間隔を使い切った後に諦めると書かれていたドキュメントの記述は誤りだった」として説明が訂正されています。配列の長さを再試行回数の上限だと理解していると、失敗したジョブが延々とキューに残り続けることになります。
間隔を動的に決めたい場合は、失敗回数を受け取る呼び出し可能オブジェクトも渡せます。実際に->(attempts) { attempts * 10 }を渡すと、1回目から順に10秒・20秒・30秒・40秒が返りました。
打ち切りはリドライブポリシーで行う
再試行の上限はgem側ではなくSQS側で設定します。デッドレターキューを作り、ソースキューにリドライブポリシーを付けます。
{
"deadLetterTargetArn": "arn:aws:sqs:ap-northeast-1:123456789012:default-dlq",
"maxReceiveCount": "5"
}
AWSの定義ではmaxReceiveCountは「デッドレターキューへ移される前に、消費者がメッセージを受信できる回数」です。5を指定すれば5回目の失敗までは再試行され、6回目の受信でDLQへ移ります。Shoryuken側のretry_intervalsは間隔だけを決め、回数はここで決まるという分担です。したがってretry_intervalsを長い配列にするより、リドライブポリシーを必ず設定するほうが優先度は高くなります。設定していないキューでは、失敗ジョブがメッセージ保持期間の上限まで残り続けます。
キュー属性の設計そのものはAWS SQS・SNS・SESの違いと使い分け|3サービスの選定基準を比較やSQS・Lambda・EventBridgeを用いた非同期処理の実践と運用方法で扱っています。
再試行させたくない失敗の扱い
入力データが不正な場合など、何度試しても成功しない失敗まで再試行するのは無駄です。ワーカーのshoryuken_optionsにnon_retryable_exceptionsを指定すると、該当する例外が出たメッセージは再試行されずに削除されます。値には例外クラスの配列のほか、例外を受け取って真偽を返す呼び出し可能オブジェクトも渡せるため、メッセージ本文で恒久的な失敗かどうかを判定する使い方もできます。あわせてauto_deleteの扱いも押さえておいてください。trueにするとジョブが例外なく終わった時点で自動削除されますが、falseのままだとワーカー内でsqs_msg.deleteを呼ばない限りメッセージが残り、可視性タイムアウト経過後に再配信されます。処理は成功しているのにジョブが繰り返し実行される場合、この削除漏れが原因です。
6.x系から7.0系への移行で確認する項目
| コード側の変更点 | 影響 | 対応 |
|---|---|---|
| 専用エラークラスの導入 | rescue の条件が外れる | Shoryuken::Errors 配下へ変更 |
| FIFO遅延の例外型変更(7.0.1) | rescue ArgumentError が素通り | FifoDelayNotSupportedError を捕捉 |
| Shoryuken::Shutdown の削除 | 参照箇所で NameError | 該当コードを削除 |
| core_ext.rb の廃止 | 拡張メソッドが消失 | Helpers 配下の関数へ置換 |
手が止まりやすいのはエラークラスの整理です。6.x系では不正な設定もキュー未検出もArgumentErrorやRuntimeErrorで上がっていたため、それらを広く捕まえるコードが残っている現場は少なくありません。7.0系の例外はShoryuken::Errors::BaseErrorの子でArgumentErrorを継承しないので、既存のrescueを素通りして落ちます。移行時はrescue ArgumentErrorとrescue RuntimeErrorを全文検索し、Shoryuken由来の例外を捕まえている箇所をShoryuken::Errors::BaseErrorへ寄せてください。公式にも7.0への移行ガイドがWikiとして用意されています。
よくある質問
Shoryukenの読み方と、gemとしての正式な入手先は?
読み方は「しょうりゅうけん」です。格闘ゲームの技名と同じ綴りのため検索結果が混ざりますが、gemはRubyGems上のshoryuken、ソースはGitHubのruby-shoryuken/shoryukenで公開されています。以前は作者個人アカウントのphstc/shoryukenにありましたが組織アカウントへ移管され、旧URLは現在のリポジトリに転送されます。2026年8月18日時点の最新版は7.0.3です。
Railsで使うとき、SidekiqのようなWeb管理画面はありますか?
同梱されていません。ジョブの滞留状況はSQSのCloudWatchメトリクスで見ます。ApproximateNumberOfMessagesVisibleが未処理件数、ApproximateAgeOfOldestMessageが滞留時間です。失敗ジョブの再実行も画面からではなく、デッドレターキューのリドライブ機能でソースキューへ戻す操作になります。管理画面前提の運用フローを組んでいるなら、この差がそのまま移行コストです。
ジョブの多重度はどこで指定し、どこまで上げられますか?
shoryuken.ymlのconcurrencyで指定します。既定値は25で、これがそのプロセスの最大同時処理スレッド数です。ただし実際の上限を決めるのはgemではなく下流の資源で、実務ではRailsのデータベースコネクションプールが先に頭打ちになります。多重度とプールサイズは揃えてください。
FIFOキューでジョブが実行されないことがあるのはなぜですか?
重複排除IDが原因である可能性が高いです。Active JobアダプタはFIFOキュー宛のメッセージに、ジョブ内容から生成したSHA-256を重複排除IDとして自動的に付けます。同じジョブクラスに同じ引数を渡すと毎回同じIDになり、AWSの5分間の重複排除インターバル内では2件目以降が破棄されます。エラーは出ません。同一内容でも投入回数だけ実行したい場合は、7.0.3以降でShoryuken.active_job_fifo_message_deduplication = falseを設定してください。
retry_intervalsに指定した回数を超えたらジョブは諦めますか?
Shoryuken独自ワーカーのretry_intervalsは諦めません。[1, 5, 25, 125]なら4回目以降はずっと125秒間隔で再試行が続きます。打ち切りたい場合はSQS側でデッドレターキューとリドライブポリシーを設定し、maxReceiveCountで受信回数の上限を決めてください。なおActive Job経由ならretry_onが既定で5回まで(attempts: 5)という別系統の制限を持ちます。