輻輳崩壊とバックプレッシャの伝播
なぜ負荷が引いても落ちたシステムが復旧しないのか。メタステイブル障害と輻輳崩壊の機構を解き、バックプレッシャと適応的同時実行制限で自力回復する原理を掴めます。
- 1.輻輳崩壊は、有効な処理が進まないのに資源だけ食い潰される状態。負荷が元に戻っても自己強化ループが続き、低スループットに張り付くメタステイブル障害になる。
- 2.無制限キューはレイテンシを無限に伸ばし、タイムアウト後の再送が無効仕事を増やす。バックプレッシャは『これ以上受けない』信号を上流へ伝播させ、流入そのものを絞る。
- 3.AIMD/Vegas風の適応的同時実行制限は、レイテンシ上昇を輻輳の兆候として並列度を自動調整する。固定上限より環境変化に強く、過負荷の入口で流量を律する。
負荷が引いても復旧しないのはなぜか
過負荷で落ちたサービスが、トラフィックを元の水準まで絞っても 低スループットに張り付いたまま戻らない——運用で最も厄介な障害の一つです。CPU やメモリに余裕が見えるのにスループットだけが回復しない。再起動すると嘘のように直る。この「いったん悪い状態に入ると、原因(負荷スパイク)が消えても自力で抜け出せない」挙動を メタステイブル障害(metastable failure) と呼びます。
その中核にあるのが 輻輳崩壊(congestion collapse) です。これは元々 TCP の文脈で観測された現象で、「ネットワークが飽和すると、有効に届くデータ量がほぼゼロまで落ち込む」状態を指します。分散システムでも同じ機構が働きます。本記事では、無効仕事の増殖がどう自己強化ループを作るのか、そして バックプレッシャ と 適応的同時実行制限 がなぜそれを断てるのかを原理から解きほぐします。
輻輳崩壊の機構:無効仕事の増殖
健全な系では、投入した処理資源が 有効スループット(goodput、実際に成功して返る仕事) に変換されます。崩壊状態では、資源は消費されるのに goodput がほぼ出ません。資源が 無効仕事(タイムアウトで誰も受け取らない応答、リトライで二重になった処理、廃棄される中間結果) に吸われるからです。
この転換は次のループで起きます。
1. 負荷スパイクで処理が遅延し、キューに滞留が溜まる
2. クライアントがタイムアウト → 応答が返る頃には「もう要らない」
3. サーバーは破棄される運命の応答に資源を使い続ける(無効仕事)
4. クライアントは失敗とみなし再送 → 流入がさらに増える
5. キューがいっそう伸び、より多くの処理がタイムアウトする → 1へ
ポイントは、この4と5が 正のフィードバック を成すことです。負荷スパイクという最初の引き金(トリガ)が去っても、「タイムアウト→再送→さらに遅延→さらにタイムアウト」のループ自体が燃料を自給します。これがメタステイブルの本質で、系には「健全な高スループット状態」と「崩壊した低スループット状態」という二つの安定点があり、トリガが系を後者へ突き落とすと、負荷を下げただけでは前者へ戻れません。
メタステイブル障害の診断で最も誤りやすいのが、引き金と持続要因の混同 です。負荷スパイク(トリガ)はとうに消えているのに、再送・キャッシュのコールドスタート・リトライストームといった持続要因がループを回し続けます。だから「原因のスパイクは収まったのに直らない」。復旧には持続要因を断つ介入(流入遮断・キュー破棄・キャッシュ再加熱)が要り、トリガを取り除くだけでは不十分です。
キュー無限増大:バッファが敵に回る瞬間
崩壊を増幅する最大の構造が 無制限キュー(unbounded queue) です。直感に反して、バッファを大きく取ると事態は悪化します。
到着率が処理率を上回る限り、キュー長は時間に比例して伸び続けます。キューが深いほど、新しく入ったリクエストが処理されるまでの待ち時間(滞留時間)も伸びます。やがて 待ち時間がクライアントのタイムアウトを超える 点を越えると、キューの中で順番待ちしている仕事は、処理される前から「完成しても無効」が確定します。サーバーは、誰も受け取らない応答を作るために全力で働く——これが輻輳崩壊そのものです。待ち行列が深いほどテイルレイテンシが発散する理屈は/devops/queueing-theory-tail-latency/の 1/(1−ρ) と地続きです。
この「巨大バッファが遅延を膨らませて害になる」現象は、ネットワークでは バッファブロート(bufferbloat) として知られます。対策の方向は明快で、キューは浅く有界に保ち、溢れたら速やかに捨てる こと。古い滞留分を優先的に破棄する手法(CoDel に代表される、滞留時間ベースの能動的キュー管理)は、「待たせ続けるより落とす方が親切」という思想に立ちます。
最悪の組み合わせは、上限のないキュー と 予算のない再送 です。キューはいくらでも仕事を受け、クライアントはいくらでも再送する。この二つが揃うと、トリガ一発で系は崩壊状態へ落ち、二度と自力で戻れません。キューに上限を設けて即時に拒否(fail fast)し、再送には予算をかける——両方が揃って初めてループを断てます。再送側の制御は/devops/retry-backoff-jitter/で詳述しています。
バックプレッシャ:流入を上流へ押し返す
崩壊を防ぐ最も根本的な手は、そもそも受け入れる量を捌ける量に合わせる ことです。これを実現するのが バックプレッシャ(背圧) です。下流が飽和したら「これ以上は受けられない」という信号を上流へ伝播させ、上流が送出ペースを落とす——プッシュ型の垂れ流しを、需要に応じたプル型の引き込みに変える仕組みです。
伝え方には段階があります。
| 方式 | 信号の伝わり方 | 上流の反応 | 特徴 |
|---|---|---|---|
| 明示的シグナル | 受信側がクレジット/ウィンドウを通知 | 許可された分だけ送出 | Reactive Streams・gRPCフロー制御。最も精密 |
| ブロッキング | キュー満杯でput()が待たされる | 送り手スレッドが自然に停止 | 有界キューで自動的に成立。実装が簡単 |
| 負荷遮断(ロードシェディング) | 受信側が即座に拒否(429/503) | 再送・代替・諦め | 最終防衛線。goodputを守るため捨てる |
| 黙殺(無策) | 信号なし、キューに溜め続ける | 送り続ける | 輻輳崩壊への直行便(避けるべき) |
理想は明示的なクレジット制御(送り手は受け手が許可した分しか送れない)ですが、系の入口では 負荷遮断(load shedding) が現実的な最終防衛線になります。捌ける分だけ受理し、超過分は早期に 429 Too Many Requests などで弾く。一見冷たく見えますが、全リクエストを道連れに崩壊させるより、一部を確実に捌いて goodput を守る 方が、系全体の効用は高い。サービスメッシュのサイドカー(/devops/service-mesh/)はこの遮断とフロー制御を組み込みで提供します。
重要なのは、バックプレッシャが 連鎖の最下流から最上流まで伝播 する点です。A→B→C の経路で C が詰まれば、B への背圧が立ち、それが A へ、最終的に クライアントへの流入制限 まで遡って初めてループが止まります。途中のどこかでバッファに溜めて「受けたふり」をすると、そこで信号が消え、崩壊が再発します。
適応的同時実行制限:レイテンシを舵にする
バックプレッシャを「どこで・どれだけ絞るか」を自動で決めるのが 適応的同時実行制限(adaptive concurrency limit) です。固定の上限(例:同時256接続まで)は、ハードウェア更新・依存先の遅延変動・デプロイで簡単に陳腐化します。低すぎれば資源を遊ばせ、高すぎれば崩壊を招く。そこで 実測レイテンシをフィードバックに、許容並列度を動的に調整 します。
発想の源は TCP の輻輳制御です。代表的な二系統があります。
# AIMD(加算増加・乗算減少): TCP Renoの古典
正常時 : limit = limit + 1 # 少しずつ攻める(加算増加)
輻輳検知時 : limit = limit * 0.5 # 一気に引く(乗算減少)
→ ノコギリ波で容量上限を探り続ける。損失(タイムアウト)を信号に使う
# Vegas風(遅延ベース): 損失ではなく「遅延の増分」で察知
gradient = RTT_min / RTT_current # 0〜1。混むほど小さくなる
new_limit = limit * gradient + allowance
→ キューが溜まり始めた初期段階で、損失が出る前に絞る
AIMD は パケット損失(≒タイムアウト)が出てから 反応するため、すでにキューが溢れた後の対処になりがちです。一方 Vegas 風の遅延ベース は、RTT_min(混雑のない最良応答時間)と現在の応答時間を比べ、遅延が伸び始めた=キューが溜まり始めた 初期兆候で並列度を絞ります。損失が出る前に手を打てるため、崩壊の入口でブレーキを踏めるのが利点です。Netflix の concurrency-limits や Envoy の adaptive concurrency はこの遅延勾配方式を実装しています。
同時実行数の上限 L は、リトル則 L = λ × W を介してスループットと滞留時間に直結します。L を上限で固定すると、応答時間 W が伸びたとき受理できる到着率 λ が自動で下がる——つまり 同時実行制限はそれ自体がバックプレッシャ装置 です。さらに L 個の枠が埋まったら新規を即拒否すれば、暗黙のうちに有界キューとして働き、キュー無限増大を構造的に封じます。適応制御は、この L を環境に合わせて呼吸させる仕組みだと捉えると見通しが良くなります。
設計と運用への落とし込み
輻輳崩壊への防御は、単一の銀の弾丸ではなく 層の組み合わせ で成立します。
- キューを有界にする:すべての境界(スレッドプール・接続プール・メッセージキュー)に上限を設け、溢れたら fail fast。深いバッファで隠さない。
- 滞留時間で捨てる:処理開始時に「もうタイムアウト済みでは」を判定し、無効仕事を実行前に破棄する。デッドラインを下流へ伝播させる(deadline propagation)。
- 適応的に絞る:固定上限ではなくレイテンシ駆動の同時実行制限で、依存先や時間帯の変動を吸収する。
- 再送に予算を与える:持続要因の筆頭が再送ストーム。再試行予算とジッタで増幅率を有界化する(/devops/retry-backoff-jitter/)。
- 観測する:goodput と総スループットの乖離、キュー長、拒否率、レイテンシ勾配をメトリクス化する(/devops/observability/)。両安定点の存在は、障害注入で意図的にトリガを与えて検証するのが上級者の作法です(/devops/chaos-engineering/)。
最後に強調すべきは、メタステイブル障害が 自動復旧を前提にできない ことです。負荷が引いても戻らない以上、運用 Runbook には「流入を一時遮断してループを冷ます」「キューを能動的にパージする」「キャッシュを再加熱してから開放する」といった 持続要因を断つ手順 を明記しておく必要があります。
まとめ
- 輻輳崩壊 は、資源が無効仕事(タイムアウト後の応答・二重再送)に吸われ、goodput がほぼゼロに落ちる状態。再送ループが自給するため負荷が引いても戻らない メタステイブル障害 になる。
- トリガ(負荷スパイク)と持続要因(再送・キュー滞留)は別物。復旧には持続要因を断つ介入が要る。
- 無制限キューは敵。深いバッファは滞留時間をタイムアウト超まで伸ばし崩壊を加速する。キューは有界に保ち、溢れたら即拒否し、古い滞留は捨てる。
- バックプレッシャ は飽和信号を最下流から最上流(クライアント流入)まで伝播させ、受け入れ量を処理能力に合わせる。入口の最終防衛線は負荷遮断。
- 適応的同時実行制限(AIMD/Vegas風) はレイテンシを舵に並列度を自動調整。遅延ベースは損失が出る前の初期兆候で絞れるため、崩壊の入口でブレーキを踏める。
DevOps/インフラ Article
輻輳崩壊とバックプレッシャの伝播を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
輻輳崩壊
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
無制限キューはレイテンシを無限に伸ばし、タイムアウト後の再送が無効仕事を増やす。バックプレッシャは『これ以上受けない』信号を上流へ伝播させ、流入そのものを絞る。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「輻輳崩壊 / バックプレッシャ」に近いか確認する。
- 強みである「輻輳崩壊は、有効な処理が進まないのに資源だけ食い潰される状態。負荷が元に戻っても自己強化ループが続き、低スループットに張り付くメタステイブル障害になる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。