1 リトライ戦略の概要

1.1 背景と目的

リトライ戦略は、処理が一時的な要因で失敗した場合に、一定条件のもとで再試行を行い、全体としての成功率と可用性を高める手法である。現代分散システムでは、ネットワークの揺らぎ、混雑、外部サービス応答遅延、短時間の障害など、成功と失敗が短い時間スケールで揺れる要素が多い。これらに対し単発の失敗を直ちに諦めると、問題の原因が回復可能でも処理が停止する。リトライはこの停止を抑え、ユーザーや上位処理への影響を軽減することを目的とする。

また、単純な再実行は成功しない場合に負荷を増やし、連鎖的な悪化を招き得るため、回数制限や待機時間の制御、失敗種類に応じた分岐、観測に基づく調整を組み合わせて運用可能な形に落とし込む必要がある。

1.2 対象となる失敗の種類

リトライ戦略の成否は「再試行すべき失敗」と「再試行してはいけない失敗」を区別できるかに依存する。失敗には恒久的に復旧見込みが乏しいものと、時間経過により回復する可能性が高いものがある。前者に対して再試行を続けると、計算資源や通信枠を浪費し、別の障害を誘発する。

1.2.1 一時的エラー

一時的エラーは、短時間で解消することが期待できる失敗である。例として、混雑による一時的な拒否、瞬間的なネットワーク切断、タイムアウト、相手側の過負荷に伴う一時応答不良などが挙げられる。これらでは再試行により別の経路や待ち時間の後に成功する余地があるため、条件付きでリトライを適用する価値が高い。

1.2.2 恒久的エラー

恒久的エラーは、時間が経っても原因の解消が見込みにくい失敗である。典型的には、入力の不正、必須パラメータ欠落、権限不足、存在しないリソース指定などが該当する。これらを再試行しても同じ結果になりやすく、成功率は上がりにくい一方で負荷が増えるため、リトライ対象から除外する設計が基本となる。

1.2.3 タイムアウトと接続失敗

タイムアウトは、応答が規定時間内に得られないという計測上の失敗であり、原因がネットワーク遅延、相手側処理の遅れ、経路の不安定さに起因する場合がある。接続失敗も同様に、相手が一時的に利用不能な期間や通信の不安定が原因となることがある。このため、タイムアウトや接続失敗はリトライ候補になりやすいが、同時に「処理自体が完了しているか不明」という不確実性が生じる。特に後段の副作用を伴う操作では、重複実行を避ける仕組み(後述の冪等性)とセットで扱う必要がある。

1.3 リトライの代表的な効果

適切に設計されたリトライは、成功率だけでなく復旧の体感やシステム全体の安定性に影響を与える。

1.3.1 成功率の向上

短時間の失敗を吸収することで、最終的に完了する割合が高まる。特に外部依存や通信経路が複数ある環境では、同一条件で再試行するだけで成功確率が増えることがある。ただしこれは「再試行が意味を持つ失敗」に限られる。誤った判定は成功率の改善を相殺し、逆に失敗を増やし得る。

1.3.2 復旧時間の短縮

障害や混雑が回復するまで待つ運用では、上位側が即時に失敗して再入力や手動対応に移ることがある。リトライは自動的に再試行するため、ユーザーや呼び出し元の待機・再手続きが短縮され、結果として復旧の体感時間が短くなる傾向がある。

1.3.3 障害の影響範囲の低減

リトライを条件付きで実行すると、失敗が発生しても即座に全体停止に至りにくい。ただし制御を誤ると負荷増で障害が拡大する。したがって、待機時間や上限、サーキットブレーカとの併用により、負荷の連鎖を抑えながら吸収するのが重要である。

2 基本設計の考え方

2.1 冪等性と副作用

リトライは失敗時に同一操作を繰り返す可能性を持つ。そのため、同じ入力に対して再実行しても結果が矛盾したり、副作用が累積したりしない性質があるかが中核となる。これが冪等性である。

2.1.1 冪等性の確認方法

冪等性の確認は、対象操作の意味論分解し、「同一要求が繰り返されたときの最終状態が変わらないか」を点検することで行う。

2.1.1.1 リクエスト識別子重複排除

代表的な手法として、リクエストに一意の識別子を付与し、サーバ側で処理済みかどうかを判定して重複を排除する方法がある。識別子に基づく記録(一定期間の保持やキャッシュ、またはストレージへの索引)により、同じ操作が繰り返されても二重計上や二重発行を防げる。識別子の生成方法と保持期間の設計は、誤った重複排除やメモリ圧迫の双方に注意が必要である。

2.1.2 非冪等処理の扱い

非冪等な操作では、リトライのたびに副作用が増幅する危険がある。たとえば課金や在庫引当、外部システムへの発注などが該当することが多い。対策としては、(1) 可能な範囲で操作を冪等化する、(2) 成功の成否を判定できる照会(結果確認)を導入して二重実行を避ける、(3) リトライ対象の条件を厳格化し、再試行しても安全な失敗だけに限定する、などが用いられる。設計判断は業務要件と監査・追跡の両立を前提とする。

2.2 リトライ条件の設計

リトライ条件は、いつ再試行するか、いつ諦めるかのルールである。単に「失敗したら」ではなく、応答の意味や例外の分類に基づいて制御する。

2.2.1 ステータスコード別判断

HTTPのようなプロトコルでは、応答ステータスコードが失敗の性質を示す手掛かりになる。一般に、クライアント側の入力不備を示す種別は再試行しても改善しにくく、サーバ側の一時的障害や過負荷を示す種別は再試行の余地がある。実装では、コードの体系だけでなく、実際の運用で観測される意味(例外的な運用、ゲートウェイの変換)も踏まえてルールを確定させる。

2.2.2 例外種別とエラーメッセージの扱い

RPCやソフトウェア例外では、例外クラスやエラーメッセージ、エラーコードが分類に使われることがある。重要なのは、メッセージの文言に依存しすぎない設計である。文言は変わりやすく、地域化や改修で意味が変化し得る。より安定した指標として、コード体系、例外のカテゴリ、内部状態フラグなどを用い、失敗理由から安全な再試行可能性を推定する。

2.3 回数・時間の制限

リトライは「無限に行わない」ことが前提条件として要求される。無制限な再試行は、負荷の増大、スレッド枯渇、キューの膨張、障害の増幅を招く。

2.3.1 最大リトライ回数

最大回数は、処理が成功しない場合の損失を上限で拘束するために使われる。回数が多いほど成功率の余地は増えるが、失敗時の浪費も増える。対象処理の重要度、許容できる遅延、下流の耐障害性を踏まえ、現実的な上限を設定する。

2.3.2 総タイムアウト

総タイムアウトは、リトライ全体で許される時間枠を制約する。たとえば各試行に個別タイムアウトを設けても、待機が長く連鎖すれば全体として待ち続ける事態が起こる。総枠を設けることで、上位の要求やユーザー体験に対する整合性を保ちやすくなる。

2.4 失敗時のフォールバック

リトライはあくまで「回復の可能性がある間の自動救済」である。最終的に失敗する場合に備え、フォールバックを決めておくことで、システムの振る舞いを予測可能にする。

2.4.1 別手段への切り替え

失敗した操作を別ルートで処理する設計がある。例として、別の外部プロバイダへの切替、ローカルキャッシュや代替データの利用、同期から非同期へのモード変更などが該当する。切替条件は、リトライが効かないと判断できた時点に合わせると無駄な試行を減らせる。

2.4.2 キュー投入や保留

即時応答が困難な場合、処理をキューに載せて後で再処理する選択肢がある。これは呼び出し側の待ち時間を短縮しつつ、後段のワーカーで制御された再試行を行える利点がある。保留には順序性や重複排除の設計が伴うため、識別子の設計とセットで検討されることが多い。

3 待機時間制御アルゴリズム

3.1 固定間隔

固定間隔は、各リトライの前に同じ待機時間を入れる方式である。実装が簡単で予測しやすい一方、障害が長引く場合や多数クライアントが同時に再試行する状況では、同じタイミングで再アクセスが集中しやすい。結果として、回復の瞬間に同期的な負荷がかかるといった問題が起こり得る。

3.2 線形バックオフ

線形バックオフは、試行回数に応じて待機時間を直線的に増やす。固定よりは負荷の増加を緩和しやすいが、増加ペースが状況に対して合わないと、なお不必要な衝突が起こる場合がある。一般に、障害の継続が短い場合には過度な待機になりにくい。

3.3 指数バックオフ

指数バックオフは、待機時間を指数的に増やす方式で、回復が遅い場合でもアクセス集中を抑えやすい。典型的には指数増加に上限を設け、待機が無限に伸びないように調整する。複数クライアントが存在する環境では、指数増加により同調が緩和される傾向がある。

3.4 ジッターの導入

ジッターは、待機時間の揺らぎを導入して再試行のタイミングが揃うことを避ける概念である。特に同じ失敗パターンを共有するクライアントが大量にある場合、揃った再アクセスは「自動復旧」の名のもとで負荷の山を作り得る。ジッターはその緩和策として広く用いられる。

3.4.1 一様ジッター

一様ジッターは、ある範囲内の値を一様分布として選び、待機時間に加える方式である。実装が比較的わかりやすく、再試行の位相が散りやすい。範囲設定が適切でない場合、揺らぎが小さく効果が薄い、または待ちが長くなりすぎるといったトレードオフが生じる。

3.4.2 フルジッター

フルジッターは、バックオフ計算で得られた上限などに対して、より広い範囲から待機時間を生成し、揺らぎを大きくする発想である。調整によっては固定的な位相が作りにくくなり、同期の問題をさらに緩和できる。ただし上限や総時間制限との整合が必要になる。

3.5 上限と丸め

待機時間制御は計算上の都合と運用上の都合の両方で調整が求められる。上限を設けないと遅延が過大になり、丸めの方式によっては時刻が再び揃ってしまうこともある。

3.5.1 最大待機時間

最大待機時間は、各試行前の待機が一定値を超えないように制限する。これにより、長期障害時でも要求全体が破綻しにくくなる。一方で上限が低すぎると指数増の利点が薄れ、再集中を抑えにくくなるため、運用データに基づく調整が望ましい。

3.5.2 スロットリングとの整合

スロットリングは、リトライとは別軸で呼び出し頻度を制御する。待機時間の設定とスロットリングの制限が競合すると、過剰に遅くなる、あるいは制限が効かずに突発負荷が増えるなどの偏りが起こる。両者は「一つの合成制御」として設計し、最大到達率や同時実行数の整合を取ることが重要になる。

3.6 同期リトライの回避

同期リトライは、失敗の発生時刻が揃うことで再試行のタイミングが一致し、負荷が段階的に増幅される現象である。アルゴリズムの選択だけでなく、実装の細部も影響する。

3.6.1 サーバ過負荷の抑制

再試行が同時に起きると、回復した直後にサーバへ再集中し、即座に再度の過負荷を招き得る。ジッター、待機上限、同時実行数の制限などを組み合わせることで、過負荷の波をなだらかにする。

3.6.2 フリクション(競合)の軽減

競合とは、複数の処理が同じ資源(ロック、在庫、同一レコード)を同時に奪い合う状態である。リトライは短時間に同じ操作を繰り返すため、ロック競合を長引かせる要因にもなる。バックオフと識別子による重複排除、必要に応じたロック粒度の見直しにより、競合を緩和する設計が有効である。

4 実装パターンと運用

4.1 クライアント側リトライ

クライアント側リトライは、呼び出し元が失敗を検知し再試行する方式である。呼び出しの粒度が細かいほど制御の自由度が高くなる一方、呼び出し元の数だけ再試行が増える可能性もある。

4.1.1 HTTPクライアントでの実装方針

HTTPでは、タイムアウト、リトライ可否、待機制御、そしてボディ再送の扱いが重要である。ボディがストリームである場合、再送に必要なバッファリングが負担となるため、読み直しの可否を確認する。さらに、接続失敗や読み取りタイムアウトといった段階で、サーバ側が処理を完了していた可能性がある点に留意し、冪等性の担保を前提に設計する。

4.1.2 RPCやメッセージングでの実装

RPCやメッセージングでは、失敗の表現が多様である。リトライの判定にはステータスだけでなく、通信層の区分(接続、送信、受信、処理)も含めて考える必要がある。メッセージの場合は、少なくとも「重複配信を許容する設計」か「同一メッセージの重複排除」を前提にしないと、二重処理が発生する。

4.2 サーバ側リトライ

サーバ側リトライは、内部で外部依存を呼ぶ際に行うことが多い。呼び出し元が少ない場合、制御が集約されやすく、過剰な同時再試行を抑えやすい利点がある。

4.2.1 下流依存先への再実行

サーバがデータベースや外部APIなどの下流へ依存している場合、下流の一時障害を吸収するために再実行が用いられる。ただしサーバ自身が多重呼び出しを行っていると、下流に対する総負荷が増えやすい。依存先ごとの回復特性に合わせて、待機時間や上限を調整することが求められる。

4.2.2 要求処理の整合性確保

サーバ側のリトライでは、同一要求が複数経路で処理される可能性を考慮し、最終状態を整合させる必要がある。典型的には、重複排除のための識別子管理、トランザクション境界の設計、部分成功の扱いが検討対象となる。整合性が担保できない場合、リトライは一時救済ではなくデータ不整合の増幅器になり得る。

4.3 非同期処理との組み合わせ

非同期処理では、呼び出し元の即時応答を切り離し、後段のワーカーが再試行を担う。これは遅延許容度が高い業務に適しやすい。

4.3.1 ジョブ再試行

ジョブ再試行は、タスクキューに失敗したジョブを戻し、一定条件を満たしたときに再実行する方式である。失敗理由に応じた再試行条件、指数バックオフとジッターの適用、並列度の制御などをジョブ基盤の機能として組み込める。ジョブのライフサイクル管理により、無限実行の危険を回避しやすい。

4.3.2 デッドレターキュー

デッドレターキューは、再試行しても解決しないジョブやメッセージを隔離する仕組みである。ここに隔離することで、以後の無駄な処理負荷を抑え、原因調査の対象を明確にできる。隔離の判定条件(回数、経過時間、エラー分類)を適切に設計し、調査可能なメタデータを保持することが運用上の要点になる。

4.4 観測とチューニング

リトライは理論だけで最適化しにくく、観測データを通じた調整が実務上の中心になる。

4.4.1 ログ設計(試行回数・理由)

ログには、試行回数、失敗理由の分類、待機時間、最終結果などを残す。これにより、なぜリトライしたか、どの段階で諦めたかが追跡可能になる。個人情報や機密情報の扱いには配慮し、必要最小限の記録に留める設計が望ましい。

4.4.2 メトリクス(成功率・遅延・回数分布)

成功率、試行ごとの成功分布、遅延の増加、回数の分布などを指標として集計する。成功が特定の試行回数に集中しているなら上限を見直せるし、遅延が過大なら待機制御を緩めるあるいは対象条件を絞る判断ができる。回数分布は、想定外の大量リトライや設定ミスの早期検知に役立つ。

4.4.3 アラート閾値の設定

アラート閾値は、平常時の揺らぎと異常の境界を定める作業である。失敗率の急増、リトライ回数の急上昇、総遅延の逸脱、デッドレターの増加などを組み合わせることで、原因特定までの時間を短縮できる。閾値は過敏すぎると通知疲れを招くため、段階的な運用(警告→危険)を設計する。

4.5 関連パターン

リトライは単独でも機能するが、周辺の設計パターンと組むことで効果が安定する。

4.5.1 サーキットブレーカ

サーキットブレーカは、一定の失敗が観測された場合に呼び出しを一時的に遮断し、回復まで待つ方式である。リトライと組むことで、失敗が続く局面で無駄な再試行を止められる。開閉の判定期間、回復確認の頻度、遮断中のフォールバックといった設計が重要になる。

4.5.2 タイムアウト設計

タイムアウトはリトライの前提である。短すぎると成功し得る処理を誤って失敗扱いし、長すぎると復旧や切替が遅れる。通信経路の特性、下流の通常応答時間、そしてバースト時の遅延分布を踏まえて設定する。

4.5.3 レート制限とバックプレッシャ

レート制限は呼び出し頻度を制御し、バックプレッシャは受け手の混雑を送り手側へ反映させる考え方である。リトライは需要側の行動に影響するため、レート制限と統合しないと制御が破綻することがある。バックプレッシャが働く設計では、リトライの増加を自然に抑えられる場合がある。

4.6 セキュリティと安全性

リトライは失敗時に再試行する性質上、情報の扱いや認証周りの安全性にも配慮が必要である。特にログや再送が原因で意図せず情報が露出することがある。

4.6.1 リトライによる情報漏えいリスク

再試行に伴い、同じエラーが複数回記録されたり、例外の詳細が繰り返し出力されたりする。ログが外部に送信される環境では、秘匿すべき値が何度も保存されるリスクが増える。対策として、エラーメッセージのマスキング、ログの冗長性抑制、アクセス権の管理などが採られる。

4.6.2 認可・認証失敗時の扱い

認可・認証エラーは、多くの場合恒久的であり、再試行しても成功しないか、あるいは誤った試行を増やすことになる。さらに、トークンの不正や期限切れの場合には刷新手順が必要であり、単純なリトライでは対処できない。リトライ対象から外し、適切な再認証フローやキー更新の経路に切り替える設計が基本となる。

5 よくある落とし穴

5.1 リトライ対象の誤判定

ステータスや例外分類を形式的に扱い過ぎると、本来再試行すべきでない失敗を繰り返してしまう。逆に、回復見込みがある失敗を除外すると成功率が伸びない。運用で観測された実データに基づき、判定ルールの更新が必要になる。

5.2 無限リトライや過剰試行

上限が設定されていない、あるいは総時間の制約が欠けていると、障害時に要求が滞留し続ける。結果として、キューが増大し、他の処理が遅延し、さらにタイムアウトが増えるという悪循環が起こり得る。上限、総枠、フォールバックを必ず設けることが重要である。

5.3 同時大量アクセスによる雪崩

多数のクライアントが同じイベントをきっかけに同時に失敗し、同時に再試行すると負荷の山が作られる。固定間隔のみの設計や、ジッターの不足はこのリスクを高める。ジッター、同期の分散、サーバ側の遮断機構といった複合対策が望ましい。

5.4 冪等性を無視した副作用

非冪等な操作で重複が起きると、二重計上や重複送信などの顕在化した不具合につながる。タイムアウトと同時に、実際には処理が完了している可能性を見落とすと誤った再実行が発生する。副作用の有無を棚卸しし、必要な重複排除を組み込むことが要点である。

5.5 指標の不足による調整不能

リトライがどの程度効いているか、どの失敗が多いか、どの程度遅延が増えたかが観測できないと、改善の指針を得られない。ログやメトリクスが欠けると「とりあえず増やす/減らす」の試行錯誤になりやすく、結果として品質が不安定になる。

5.6 ユーザー体験への悪影響(遅延・二重送信)

リトライは内部の安定性を高める一方、応答が遅れることでユーザー体験が悪化する場合がある。さらに、画面側で再送ボタンが押されるなど、ユーザー側の行動と競合すると二重送信が起こり得る。画面の状態管理、タイムアウト表示、操作の再実行防止なども含めて全体の体験を設計することが求められる。