再試行・指数バックオフ・ジッタの設計理論
素朴な再試行はなぜ障害を増幅させるのか。指数バックオフとジッタ、再試行予算という3つの原理を押さえれば、落ちかけたシステムを自分の手でとどめを刺さず守れるようになります。
- 1.固定間隔の再試行は、失敗が同時多発したクライアントを同じ時刻に再送させ、負荷の波が周期的に重なるリトライストームを生む。
- 2.指数バックオフで間隔を倍々に伸ばし、ジッタ(full/equal/decorrelated)で各クライアントの再送時刻をばらすことで、この同期を壊す。
- 3.間隔調整だけでは総再送数は減らない。再試行予算(トークンバケット方式の上限)で全体の増幅率そのものを抑えるのが本丸。
なぜ素朴な再試行が障害を増幅するのか
一時的な失敗を再送でリカバリするのは正しい発想です。問題は 失敗のしかたが相関している ことにあります。あるサービスが過負荷で多数のリクエストを取りこぼすと、それらを投げていたクライアントは ほぼ同時刻に失敗を検知 します。固定間隔(例:1秒後に再試行)で組んでいると、全員が同じ1秒後に揃って再送します。
ここで増幅が起きます。本来 N 件のリクエストが、最大 K 回再試行する設計なら、ワーストケースで N×(K+1) 件まで膨らみます。過負荷で弱っているサーバーに、平常時の数倍の負荷が 同期した波 として襲いかかる。これが回復を妨げ、次の波がまた重なる——この自己増幅ループが リトライストーム(再試行嵐) です。
再試行が発動するのは相手が失敗を返したときです。つまり再試行のトラフィックは、最も負荷を捌けない瞬間に集中して 降り注ぎます。健全なときは無害でも、過負荷時には傷口を広げる方向に働く。この非対称性が、素朴な再試行の本質的な危うさです。
分散システム(/devops/microservices/)では、A→B→C と呼び出しが連鎖します。各層が独立に最大3回再試行すると、増幅は層ごとに乗算され、最深層 C には 3×3×3=27 倍の負荷が届きうる。再試行は段ごとに掛け算で効く ため、多層構成ほど制御が要ります。
指数バックオフ:間隔を倍々に伸ばす
第一の対策は 指数バックオフ です。再試行のたびに待ち時間を指数的に伸ばし、失敗が続くほど送出ペースを落とします。基本式は次の通りです。
delay = base * (multiplier ^ attempt) # 例 base=100ms, multiplier=2
# attempt 0,1,2,3 → 100ms, 200ms, 400ms, 800ms
delay = min(delay, cap) # 上限(cap)で頭打ちにする
multiplier=2 がよく使われますが本質は「増やすこと」自体です。上限(cap)を設けるのは、青天井だと待ち時間が分単位に膨れ、リクエストが滞留してしまうためです。指数バックオフは 過負荷の相手に回復の時間を与える 設計であり、輻輳制御(TCP の指数的な再送タイマ)と同じ思想に立ちます。
ただし指数バックオフ だけ では同期は壊れません。同時刻に失敗した全クライアントは、同じ式で 100ms → 200ms → 400ms と進むため、波の山は薄く広がるものの 依然として揃って 訪れます。間隔を伸ばしても位相が揃っていれば、波は重なり続けるのです。
ジッタ:再送時刻をばらして同期を壊す
そこで ジッタ(jitter)——待ち時間に乱数を混ぜて各クライアントの再送時刻を意図的にずらす手法が要ります。代表的な3方式を比較します(基準は指数バックオフ値 exp = min(cap, base*2^attempt)、prev は前回の実待ち時間)。
| 方式 | 計算式(疑似) | ばらつき | 特徴 |
|---|---|---|---|
| No Jitter | delay = exp | なし | 位相が揃い、波が重なる(避けるべき基準) |
| Full Jitter | delay = random(0, exp) | 最大 | 完全に散らばる。総待ち時間は短めに偏る |
| Equal Jitter | delay = exp/2 + random(0, exp/2) | 中 | 下限を確保しつつ散らす。間隔の予測性が高い |
| Decorrelated | delay = min(cap, random(base, prev*3)) | 大(自己回帰) | 前回値を基に揺らす。詰まり時に良好な分散 |
AWS の実験的検証では Full Jitter と Decorrelated Jitter が、サーバー側の競合と総完了時間の両面で優れた結果を示しました。重要なのは「乱数の下限を0近くまで許すか」です。Full Jitter は random(0, exp) で最小0を許すため最も散りますが、運悪く極小値を引くと過負荷の相手を早く突くリスクが残ります。Equal Jitter は exp/2 の下限を残し、散らしつつ最低待機を保証します。
# Decorrelated Jitter(AWS が提唱)
sleep = base
on retry:
sleep = min(cap, random_between(base, sleep * 3))
Decorrelated は 前回の実待ち時間を次の乱数範囲の上限(の係数)に使う自己回帰的な構造で、失敗が続くほど範囲が自然に広がり、相関を崩しながらバックオフします。
ジッタの狙いは平均間隔の最適化ではなく、多数のクライアント間の位相をばらす ことです。1台だけを見ると乱数で待ち時間が前後するだけに見えますが、全体では「同時刻に集まる山」が消える。同期した群れを、なめらかな流れに均すのがジッタの役割です。
再試行予算:増幅率そのものを抑える
ここまでで波の 形 は均せましたが、ある盲点が残ります。バックオフもジッタも 再送のタイミングをずらすだけで、総再送数は減らさない。長時間の障害では、ずれた再送がだらだらと積み上がり、平常時の数倍の総負荷がかかり続けます。タイミング制御は 対症療法 にすぎません。
本丸は 再試行予算(retry budget) です。「再試行の総量を、新規リクエスト量に対する一定割合に制限する」というクライアント側のグローバルな上限で、典型的には トークンバケット で実装します。
# トークンバケット方式の再試行予算(クライアント側で共有)
- 新規リクエスト成功ごとにトークンを少し補充
- 再試行を行うたびにトークンを1消費
- トークンが尽きたら再試行を諦め、即座に失敗を返す
→ 再試行は「新規トラフィックの ratio(例 20%)まで」に制限される
これにより、相手が長時間ダウンしても再試行による増幅は ratio 倍(例 1.2倍)以内 に固定されます。gRPC や Envoy(/devops/service-mesh/ のサイドカー)は、この予算方式を組み込みで持ちます。個々のリクエストの「あと何回試すか(per-request の上限 K)」と、クライアント全体の「再試行に割ける総量(予算)」は 別レイヤの制御 であり、両方を設定して初めて増幅を有界化できます。
「再試行の最大回数を3にした」だけでは増幅対策として 不十分 です。問われるのは三点セット——(1) 指数バックオフで送出ペースを落とす、(2) ジッタで同期を壊す、(3) 再試行予算で総量を有界化する。さらに 冪等性が保証されない操作(非冪等な書き込み)は再試行しない、という前提も忘れずに。
周辺原理:何を、どこで再試行すべきか
タイミングと総量を制御しても、そもそも再試行してよい失敗か の判定が誤っていると無意味です。
- 冪等性:同じ操作を複数回適用しても結果が変わらない性質。GET や設定上書きは安全ですが、「残高を引く」ような非冪等操作は、冪等キー(idempotency key)で重複実行を防がない限り再試行してはいけません。
- 失敗の種別:再試行が意味を持つのは 一時的失敗(タイムアウト、503、コネクションリセット)だけ。
400 Bad Requestのような恒久的失敗をいくら再送しても成功せず、増幅だけが残ります。 - 多層での重複再試行を避ける:A→B→C の連鎖では、原則 末端に近い1層だけ で再試行し、上位層は再試行しない(または予算を共有する)。各層が独立に再試行すると、前述の
3×3×3の乗算的増幅を招きます。 - サーキットブレーカとの併用:連続失敗が閾値を超えたら回路を開いて再試行ごと止め、相手に回復時間を与える。再試行とは逆向きの「諦める」制御で、ジッタ・予算と組で初めて過負荷の自己増幅を断てます。
これらが効いているかは推測ではなく観測で確かめます。再試行率・予算の枯渇回数・バックオフ後の成功率をメトリクス化し(/devops/observability/)、障害注入で実挙動を検証する(/devops/chaos-engineering/)のが上級者の作法です。
まとめ
- 固定間隔の再試行は失敗の相関を温存し、同期した負荷の波=リトライストーム を生む。多層構成では増幅が乗算で効く。
- 指数バックオフ は送出ペースを落とすが、位相は揃ったまま。ジッタ(full/equal/decorrelated)で再送時刻を散らして同期を壊す。
- バックオフとジッタは波の形を均すだけで総量は減らない。再試行予算(トークンバケット) で増幅率そのものを有界化するのが本質的対策。
- 前提として 一時的失敗のみ・冪等な操作のみ・末端1層のみ 再試行し、サーキットブレーカと観測で全体を制御する。
DevOps/インフラ Article
再試行・指数バックオフ・ジッタの設計理論を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
再試行
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 5
導入後に効く点
指数バックオフで間隔を倍々に伸ばし、ジッタ(full/equal/decorrelated)で各クライアントの再送時刻をばらすことで、この同期を壊す。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 5
判断チェックリスト
- 自社の用途が「再試行 / 指数バックオフ」に近いか確認する。
- 強みである「固定間隔の再試行は、失敗が同時多発したクライアントを同じ時刻に再送させ、負荷の波が周期的に重なるリトライストームを生む。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。