アドミッションコントロールと適応的負荷制限
過負荷で全体共倒れを防ぐには、賢く要求を捨てる設計が要ります。優先度・公平性に基づく load shedding、CoDel 風の遅延ベース判定、リトル則由来の適応的同時実行制限を原理から解説します。
- 1.過負荷時はすべてを処理しようとすると全要求が遅延し共倒れする。受理した要求だけを守り、超過分は早く安く捨てる(fail fast)のが load shedding の本質で、捨て方に優先度と公平性を設計する。
- 2.静的閾値は環境変化に弱い。CoDel 風に『キュー内滞留時間(sojourn time)が目標を超え続けたら捨てる』という遅延ベース判定なら、容量を直接知らなくても飽和を検知できる。
- 3.適応的同時実行制限はリトル則 L=λW を逆用し、観測したレイテンシ勾配から最適な並列上限を AIMD で探る。Netflix concurrency-limits が代表で、固定スレッドプールより環境追従性が高い。
なぜ「全部処理しよう」とすると全部死ぬのか
サーバーが処理できる以上の要求が来たとき、素朴な実装は すべてを受け付けてキューに積む。一見公平ですが、これが最悪の選択です。到着率がサービス率を超え続けると、待ち行列は際限なく伸び、各要求の待ち時間が膨らみます。やがて全要求がクライアントのタイムアウトを超え、処理した結果は誰も受け取らない——CPU は回り続けるのに有効スループットはゼロに近づく。これが過負荷の典型的な崩壊形です。
要点は 受理した要求すべてを守ろうとすると、結局どれも守れない という非線形性にあります。だから過負荷時は発想を逆転させ、捌ける分だけ受理し、超過分は即座に・安価に拒否する(fail fast)。これが アドミッションコントロール(受理制御) と load shedding(負荷の意図的な切り捨て) です。本記事では「どの要求を捨てるか」「いつ捨てると判断するか」「そもそも何件まで受理するか」を、待ち行列理論と制御の原理から解きます。
load shedding の鉄則は 拒否のコストを処理のコストより十分小さく することです。認証・デシリアライズ・DB アクセスまで進んでから捨てると、捨てる要求にも資源を食われ、過負荷下でますます容量が削られます。受理判定は入口(ロードバランサや L7 ゲートウェイ、ハンドラの最前段)で、できるだけ軽量な情報だけで下すのが原則です。
何を捨てるか:優先度と公平性
「捨てる」と決めたら、次は どれを捨てるか です。全要求を等価に扱う(先頭から落とす、ランダムに落とす)のは公平に見えて、しばしば最悪です。重要なヘルスチェックや決済確定が、リトライの嵐に紛れて巻き添えで落ちるからです。
第一の軸は 優先度(criticality) です。要求にクラスを付け、過負荷時は低優先から順に切り捨てます。Google の実務では、ユーザー対面の対話的要求 > バックグラウンドのバッチ > ベストエフォートの投機的処理、といった criticality を要求自体に埋め込み、過負荷時はサーバーが優先度の低いものから機械的に shed します。重要なのは 優先度をシステム全体で伝播 させることで、上流が付けた criticality を下流もそのまま尊重しないと、末端で重要要求が落ちます。これはタイムアウト・デッドラインを連鎖伝播させる発想と同じ系譜です(/devops/timeout-deadline-propagation/)。
第二の軸は 公平性(fairness) です。優先度だけだと、一人の暴走クライアント(バグやリトライストーム)が同一優先度の枠を食い尽くします。そこで送信元(テナント・ユーザー・API キー)ごとに枠を切り、過剰に消費している発信元から優先的に落とす。代表が重み付き公平キューイング(WFQ)やテナント単位のレート制限で、「全体が逼迫したとき、平均より多く使っている者ほど多く削る(max-min fairness)」のが基本思想です。
| 切り捨て方針 | 守るもの | 弱点 |
|---|---|---|
| 先頭から落とす(tail drop) | 実装が単純 | 古い順に成功し新着が全滅、リトライと相性最悪 |
| ランダムに落とす | 特定要求への偏りなし | 重要要求も同確率で巻き添え |
| 優先度順に落とす | 重要要求を最後まで守る | 優先度の伝播と詐称対策が必要 |
| 公平キューイング | 暴走クライアントから隔離 | テナント識別と状態管理のコスト |
落とされたクライアントが即リトライすると、shed したはずの負荷が掛け算で戻ってきます。load shedding は 指数バックオフ+ジッタ+リトライ予算 とセットで初めて機能します(/devops/retry-backoff-jitter/)。拒否時は Retry-After を返して再来を後ろへずらし、サーキットブレーカで上流に「今は無駄打ちするな」と伝えるのが定石です(/devops/circuit-breaker-bulkhead/)。
いつ捨てるか:静的閾値の限界と遅延ベース判定
捨てる対象を決めても、いつ「過負荷だ」と判断するか が残ります。最も単純なのは静的閾値——「同時実行 100 を超えたら拒否」「CPU 80% で shed」。だがこれは脆い。最適値はハードウェア・ペイロード・下流の調子・コードのバージョンで刻々変わり、固定値は すぐ古くなる。閾値を低く取れば余力があるのに捨て、高く取れば崩壊してから気づきます。
ここで効くのが 遅延ベースの判定、とくに CoDel(Controlled Delay) の発想です。CoDel はもともとネットワークのバッファブロート対策ですが、サーバーのキューにそのまま応用できます。鍵は キューの長さではなく、キュー内での滞留時間(sojourn time)を見る ことです。
CoDel 風の load shedding(疑似コード)
TARGET = 5ms # 許容する最小滞留時間
INTERVAL = 100ms # 観測ウィンドウ
各要求がキューから取り出される時:
sojourn = now - 要求のenqueue時刻
if sojourn < TARGET:
最小滞留がTARGET未満 → 健全。dropping状態を解除
else:
ウィンドウINTERVALの間ずっと最小滞留が
TARGET以上なら → 飽和とみなしdropを開始/継続
なぜ「長さ」でなく「滞留時間」なのか。キュー長は処理速度を知らないと意味を持ちません。同じ 100 件でも、1ms で捌けるなら問題なく、100ms かかるなら破綻です。一方 滞留時間は『今のサービス率で、この要求がどれだけ待たされたか』を直接表す。さらに CoDel は「一瞬の長さ」でなく「ウィンドウ全体で最小滞留が目標を超え続けたか」を見ます。瞬間的なバースト(一時的にキューが伸びてもすぐ捌ける)では捨てず、恒常的な飽和 のときだけ捨てる——この区別が、健全なバーストを殺さずに本当の過負荷だけを叩く肝です。
CoDel を簡略化し、キューに入った時点でデッドラインを刻み、取り出し時に期限切れなら処理せず捨てる(LIFO +期限切れ破棄)という手もあります。期限切れの要求はクライアントがもう待っていない可能性が高く、処理しても無駄。古い順に処理する FIFO よりも、新着を優先して有効スループットを保てる場面があります。いずれも「壁時計の閾値」ではなく「個々の要求の待ち時間」を判定軸に据えるのが共通の原理です。
何件受理するか:適応的同時実行制限とリトル則
shed の判定とは別に、そもそも 同時に受理する要求数(concurrency limit)の上限 をどう決めるかという問題があります。固定スレッドプールやコネクション上限はこの一種ですが、最適値は環境で変わるため静的設定では当たりません。ここで リトル則 が効きます。
リトル則: L = λ × W
L : 系内に滞在する平均要求数(≒ 同時実行数)
λ : 到着率(スループット)
W : 1要求あたりの平均応答時間(レイテンシ)
この式を逆に読むと、ある同時実行数 L のとき、達成できるスループット λ は L/W で決まる。系が健全なうちは、L を増やすほど λ も伸びます(W がほぼ一定だから)。しかし飽和点を超えると、L を増やしても λ は頭打ちで W だけが急増 する——増えた在庫が待ち行列で渋滞するだけになる。つまり レイテンシが膨らみ始める直前の L が、その瞬間の最適な同時実行上限 です。待ち行列が深まるほどテイルが発散する関係は、ここでも背骨になっています(/devops/queueing-theory-tail-latency/)。
適応的同時実行制限(adaptive concurrency limit) は、この最適点を実測から動的に探ります。Netflix の concurrency-limits が代表で、TCP 輻輳制御とよく似た AIMD(加法増加・乗法減少) で上限を調整します。
適応的同時実行制限のループ(概念)
RTTmin = これまで観測した最小レイテンシ(無負荷時の素の処理時間)
RTT = 直近の観測レイテンシ
gradient = RTTmin / RTT # 1なら渋滞なし、小さいほど渋滞
newLimit = currentLimit × gradient + allowance
if RTTが伸びてきた(gradientが小さい):
上限を乗法的に絞る → 早めに shed して渋滞を解く
else:
上限を加法的に少しずつ広げる → 余力を探りに行く
肝は RTTmin(素の処理時間)と現在の RTT の比(勾配) を渋滞のシグナルに使う点です。レイテンシが RTTmin に近ければ系は空いており上限を広げてよい。RTT が RTTmin から乖離し始めたら、それは在庫が待ち行列で待たされ始めた証拠——リトル則でいう「L を増やしても λ が伸びず W だけ伸びる」領域に入った合図なので、上限を絞ります。これにより 絶対的な容量を一切設定しなくても、システムは自分の飽和点近傍に上限を自動追従させられます。
| 観点 | 静的上限(固定プール) | 適応的同時実行制限 |
|---|---|---|
| 設定 | 容量を人手で測り固定 | レイテンシ勾配から自動追従 |
| 環境変化 | HW/下流が変わると陳腐化 | RTTmin と RTT の比で追従 |
| 過負荷検知 | 閾値超過(遅行・粗い) | レイテンシ上昇を先行検知 |
| 弱点 | 保守的すぎ or 危険すぎ | RTTmin の汚染・振動に注意 |
勾配法は RTTmin の正しさに全面依存します。長時間ずっと過負荷だと「最小レイテンシ」自体が膨らんだ値で固定され、本当の素の処理時間を見失う(RTTmin の汚染)。実装では RTTmin を定期的に再探索(一時的に上限を絞って空いた状態の素のレイテンシを測り直す)したり、減衰窓で古い観測を忘れさせます。また下流のレイテンシまで一緒に測ると、自サーバーの飽和なのか下流の飽和なのか区別できません。自分が制御できる層のレイテンシで判定する のが原則です。
三層を組み合わせる:縮退の設計として
実務では「何を捨てるか(優先度・公平性)」「いつ捨てるか(遅延ベース判定)」「何件受理するか(適応的同時実行制限)」は 排他ではなく重ねて 使います。適応的同時実行制限が入口で受理数の上限を実測追従で決め、その上限を超えた要求を優先度と公平性に従って shed し、内部キューでは CoDel/デッドラインで滞留しすぎた要求を捨てる——という多層防御です。
この設計が目指すのは graceful degradation(緩やかな縮退) です。負荷が容量を超えても、有効スループットを平坦に保ったまま、超過分だけを安く拒否する。これを怠ると、システムは閾値を境に メタステーブル故障——いったん過負荷状態に落ちると負荷が引いても自力で戻れない自己持続的な崩壊——に陥ります(/devops/cascading-metastable-failures/)。load shedding は単なるレート制限ではなく、飽和点を越えた先でもスループットを崩さないための制御 であり、輻輳崩壊を避ける backpressure の一形態だと捉えるのが正確です(/devops/congestion-collapse-backpressure/)。
load shedding は『早く安く捨てる』『重要要求を優先度で守る』『暴走発信元から公平に削る』の三点セットで説明できると強いです。CoDel が キュー長ではなく滞留時間(sojourn time) を見ること、適応的同時実行制限が リトル則 L=λW を逆用しレイテンシ勾配を渋滞シグナルにする ことを言えると上級者向けの加点になります。リトライ増幅と RTTmin 汚染は頻出の落とし穴です。
まとめ
- 過負荷で全要求を受理すると 有効スループットが崩壊 する。受理した分だけ守り、超過分は 入口で早く安く拒否(fail fast) するのがアドミッションコントロールと load shedding の本質。
- 何を捨てるか は優先度(criticality を全体で伝播)と公平性(max-min で暴走発信元から削る)の二軸。リトライ増幅を抑える
Retry-After・バックオフ・サーキットブレーカと必ずセット。 - いつ捨てるか は静的閾値より遅延ベースが堅牢。CoDel 風に キュー内滞留時間がウィンドウを通して目標を超え続けたら 捨てると、容量を知らずに恒常的飽和だけを叩ける。
- 何件受理するか はリトル則
L=λWを逆用した適応的同時実行制限で。RTTmin と現在 RTT の勾配を渋滞シグナルに AIMD で上限を追従させ、絶対容量を設定せず飽和点近傍に張り付かせる。 - 三層は重ねて使い、目標は graceful degradation。怠ればメタステーブル故障へ落ちる。
DevOps/インフラ Article
アドミッションコントロールと適応的負荷制限を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
負荷制限
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
静的閾値は環境変化に弱い。CoDel 風に『キュー内滞留時間(sojourn time)が目標を超え続けたら捨てる』という遅延ベース判定なら、容量を直接知らなくても飽和を検知できる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「負荷制限 / アドミッションコントロール」に近いか確認する。
- 強みである「過負荷時はすべてを処理しようとすると全要求が遅延し共倒れする。受理した要求だけを守り、超過分は早く安く捨てる(fail fast)のが load shedding の本質で、捨て方に優先度と公平性を設計する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。