再試行予算とトークンバケットによる増幅抑制
落ちかけた依存先に再送が殺到して傷口を広げる——その増幅を数理で押さえます。レイヤごとの積で膨らむ機構と、トークンバケット予算で増幅率を有界化する設計が手に入ります。
- 1.各層が独立に最大K回再試行すると増幅は層ごとに積で効き、L層なら最深部に最大(K+1)^L倍の負荷が届く。最大回数だけを下げても乗算構造は残る。
- 2.再試行予算はトークンバケットで実装し、再試行を新規成功トラフィックの一定比率(例10〜20%)に制限する。これで障害がいくら長引いても増幅率を1+ratio倍に固定できる。
- 3.予算はループ利得を下げる定常的な歯止め、サーキットブレーカは失敗集中時に再送ごと止める即応的な歯止め。両者を協調させてリトライストームのループ利得を1未満へ抑える。
再試行はなぜ「積」で増幅するのか
再試行の危うさは、効果が層をまたいで掛け算で積み上がる点にあります。1回のリクエストが最大 K 回まで再試行されるなら、その層を通過するリクエスト数はワーストケースで K+1 倍(初回1回+再試行K回)に膨らみます。問題は多層構成です。A→B→C のように L 層が呼び出しを連鎖させ、各層が独立に最大 K 回再試行する設計だと、増幅は層ごとに乗算されます。
# L層・各層 最大K回再試行 のワーストケース増幅率
amplification = (K+1)^L
# 例 K=3, L=3 → (3+1)^3 = 64倍
# 入力1件が、最深層では最大64件のリクエストになりうる
つまり最深層 C には、平常時の最大 (K+1)^L 倍の負荷が届きえます。K=3・L=3 という穏当に見える設定でも64倍です。各層では「たかが4倍」でも、積み重なると指数的に膨れる——これが多層再試行の本質的な落とし穴です(カスケードの力学は /devops/cascading-metastable-failures/ で詳説)。
再試行が発動するのは相手が失敗を返したときです。すなわち再送トラフィックは、依存先が最も負荷を捌けない瞬間に狙いすまして降り注ぎます。健全なときは無害でも、過負荷時には傷口を広げる方向へ働く。この非対称性のために、増幅は「平常時の平均値」ではなく「最悪時のピーク」で評価しなければなりません。
増幅をループ利得として捉える
タイミングをずらす指数バックオフやジッタ(/devops/retry-backoff-jitter/)は、同期した波を均す対症療法であり、総再送数そのものは減らしません。長時間の障害では、ばらけた再送が定常的に積み上がります。そこで増幅をフィードバックループの利得として捉え直します。
過負荷時には「再送 → 無効仕事の増加 → レイテンシ悪化 → さらに失敗 → さらに再送」という正のフィードバックが回ります。1周あたりに再送がどれだけ増えるかをループ利得 g と呼ぶと、系の振る舞いは次で決まります。
| ループ利得 | 級数の挙動 | 系の状態 |
|---|---|---|
| g が 1 未満 | 1 + g + g^2 + … = 1/(1-g) に収束 | 再送は有界。擾乱が去れば自力回復する |
| g が 1 以上 | 級数が発散し再送が雪だるま式に増える | メタステイブル障害。負荷を戻しても張り付く |
設計目標は明確です。どんな障害状況でも g を確実に1未満へ抑え込むこと。バックオフやジッタは g を多少下げますが、上限を保証しません。利得に硬い天井を与えるのが再試行予算の役割です。
再試行予算:トークンバケットで増幅率を有界化する
再試行予算(retry budget)は「再試行の総量を、新規リクエスト量に対する一定比率に制限する」クライアント側のグローバルな歯止めです。per-request の最大回数 K とは別レイヤの制御で、典型的にはトークンバケットで実装します。
# トークンバケット方式の再試行予算(クライアント内で共有)
ratio = 0.2 # 再試行は新規成功トラフィックの20%まで
min_per_sec = 10 # 低トラフィック時の最低保証(小規模時の枯渇を防ぐ)
on success(non-retry): # 新規リクエストが返るたび
tokens += ratio # トークンを ratio 分だけ補充
on retry_attempt:
if tokens >= 1:
tokens -= 1 # 1消費して再試行
do_retry()
else:
fail_fast() # 予算切れ。再試行せず即失敗を返す
tokens = min(tokens, bucket_cap) # 上限でクランプ(バースト制限)
仕組みの核心は、補充レートを成功トラフィックに連動させる点です。依存先が健全なら成功が多く、予算は潤沢に貯まる。依存先が落ちると成功が止まり、補充が枯れて再試行はすぐ予算切れになります。結果として再試行は、たとえ依存先が長時間ダウンしても新規トラフィックの ratio 倍までしか発生しません。
# 予算がかかった層の有効増幅率
amplification_per_layer = 1 + ratio # 例 1 + 0.2 = 1.2倍
# 障害が何時間続こうと、この層の負荷増は1.2倍に有界化される
# 比較:予算なしの最悪値は K+1 倍(K=3 なら 4倍)で、長時間続く
min_per_sec のような最低保証を併せて持つのが実装の定石です。トラフィックが極端に少ないとき比率だけでは予算がほぼゼロになり、正当な単発再試行まで殺してしまうためです。gRPC のリトライ設定や Envoy(/devops/service-mesh/ のサイドカー)は、この比率+最低保証つきの予算を組み込みで備えます。
per-request の最大回数 K は1リクエストが粘る上限、再試行予算はクライアント全体が再試行に割ける総量の上限です。役割が直交するため両方を設定して初めて増幅が有界化します。K を下げるだけでは (K+1)^L の乗算構造は残り、予算だけでは1リクエストが無駄に粘るのを止められません。
サーキットブレーカとの協調
再試行予算は系全体のループ利得を恒常的に押し下げる定常的な歯止めですが、単一の依存先が完全にダウンした瞬間の即応性には欠けます。ここでサーキットブレーカ(/devops/circuit-breaker-bulkhead/)が補完します。両者は止め方の時間スケールが異なります。
| 観点 | 再試行予算 | サーキットブレーカ |
|---|---|---|
| 粒度 | クライアント全体の総量を比率で制限 | 依存先(回路)単位で開閉 |
| 反応 | 成功率の低下に連動して緩やかに絞る | 失敗率がしきい値超で即 Open に落ちる |
| 止める対象 | 新規含む全体に対する再試行の割合 | Open 中は初回も再試行も丸ごと遮断 |
| 主目的 | 増幅率(ループ利得)の有界化 | 弱った依存先への呼び出しの即時停止と回復待ち |
協調の要点は多重に歯止めをかけることです。回路が Open のときは再試行どころか初回呼び出しも止まるため、予算の消費自体が起こりません。回路が Closed/Half-Open で個別失敗が散発する局面では、予算がループ利得に上限を与えます。さらに増幅を断つには、再試行を行う層を絞るのが効きます。
- 末端に近い1層だけで再試行する:A→B→C で各層が独立に再試行すると
(K+1)^Lの乗算増幅を招きます。原則は最深層に近い1層のみで再試行し、上位は再試行しない(またはデッドライン伝播で残時間を渡し、予算を共有する)。 - 一時的失敗のみ再試行する:意味があるのはタイムアウト・503・コネクションリセットなど一時的失敗だけです。
400 Bad Requestのような恒久的失敗は何度送っても成功せず、増幅だけが残ります。 - 冪等な操作のみ再試行する:非冪等な書き込みは冪等キーで重複実行を防がない限り再試行してはなりません(/devops/idempotency-exactly-once/)。
「最大再試行回数を3にした」だけでは増幅対策として不十分です。問われるのは——(1) 指数バックオフとジッタでタイミングを散らす、(2) 再試行予算(トークンバケット)で総量=ループ利得を有界化する、(3) サーキットブレーカで失敗集中時に呼び出しごと止める、(4) 再試行は末端1層・一時的失敗・冪等操作に限る。この四点が揃って初めてリトライストームを設計で封じられます。
まとめ
- 各層 最大K回・L層の独立再試行は最深部に最大
(K+1)^L倍の負荷を届ける。増幅は層ごとに積で効くため、最大回数を下げるだけでは乗算構造が残る。 - 増幅はフィードバックのループ利得 g として捉えられ、
g が 1 未満なら有界・自力回復、g が 1 以上なら自己持続する(メタステイブル障害)。設計目標は g を確実に1未満へ抑えること。 - 再試行予算はトークンバケットで再試行を新規成功トラフィックの ratio 倍に制限し、障害がいくら続いても各層の増幅率を
1 + ratioに有界化する。最低保証を併設して低トラフィック時の枯渇を防ぐ。 - サーキットブレーカ(即応的遮断)と再試行予算(定常的な比率制限)を協調させ、末端1層・一時的失敗・冪等操作に絞れば、リトライストームのループ利得を1未満へ封じ込められる。
DevOps/インフラ Article
再試行予算とトークンバケットによる増幅抑制を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
再試行予算
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
再試行予算はトークンバケットで実装し、再試行を新規成功トラフィックの一定比率(例10〜20%)に制限する。これで障害がいくら長引いても増幅率を1+ratio倍に固定できる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「再試行予算 / トークンバケット」に近いか確認する。
- 強みである「各層が独立に最大K回再試行すると増幅は層ごとに積で効き、L層なら最深部に最大(K+1)^L倍の負荷が届く。最大回数だけを下げても乗算構造は残る。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。