バッファブロートと AQM(CoDel・FQ-CoDel・PIE)
回線は空いていないのになぜ遅延だけ膨らむのか、その正体が腑に落ちる。過大バッファが遅延を肥大させる原理と、CoDelやPIEが滞留時間を直接制御する仕組みを内部から解説。
- 1.バッファブロートとは過大なバッファにパケットが滞留し、ロスは起きないのに遅延(とジッタ)が肥大する現象。
- 2.原因はキュー長で輻輳を判断する古い設計。AQM はキュー長ではなく「滞留時間」を見て早めに落とす。
- 3.CoDel は滞留時間 5ms・100ms 窓を基準にドロップ間隔を逆数平方根で詰め、PIE はキュー遅延の比例積分制御でドロップ確率を決める。
バッファブロートとは何か
バッファブロート(bufferbloat)は、ルータやモデムが過大なバッファを持つために、輻輳時にパケットがそこへ大量に溜まり、パケットロスは起きないのに遅延が肥大する現象です。直感に反するのは「ロスがない=健全」ではない点です。むしろロスが出ないほどキューが深く埋まり、その分の滞留時間がそのまま往復遅延(RTT)に上乗せされます。
なぜ過大バッファが生まれたか。メモリが安くなり、設計者が「捨てるより貯めるほうが良い」と考えてバッファを大きく取ったためです。ところがエンド間の輻輳制御は損失を輻輳のシグナルとして使います(AIMD と輻輳制御の数理 参照)。バッファが深いと送信側は損失を観測できず、cwnd を増やし続け、バッファを満杯近くまで埋めてしまいます。この「常に満杯近くで動く」状態が標準になることが問題の本質です。
ボトルネックの送出レートを C、滞留中のバイト数を Q とすると、キューイング遅延は Q / C です。100Mbps の回線に 1MB のバッファがあり満杯なら、追加遅延は 1MB / 100Mbps ≒ 80ms。回線が遅いほど、同じバッファでも遅延は大きくなります。
なぜ「キュー長」での制御は破綻するか
古典的な対策は RED(Random Early Detection)で、平均キュー長がしきい値を超えると確率的にパケットを落として送信側を早めに減速させます。考え方は正しいのですが、運用が破綻しました。
RED のしきい値はパケット数(またはバイト数)で指定します。しかし「何パケット溜まったら困るか」はリンク速度・RTT・フロー数に依存し、固定値で適切に設定するのは事実上不可能です。min_th・max_th・max_p を手で調整する必要があり、ほとんどの現場で無効化されました。
ここで Kathleen Nichols と Van Jacobson の洞察が効きます。問題なのはキューが長いことそのものではなく、キューが長く居座ることです。バースト的なトラフィックを吸収する一時的なキュー(good queue)は有益で、これを落としてはいけません。落とすべきは、いつまでも掃けない定常的なキュー(bad queue)だけです。両者を見分ける指標がキュー長ではなく、パケットの**滞留時間(sojourn time)**である、というのが AQM 第二世代の出発点です。
| 観点 | テールドロップ | RED | CoDel / PIE |
|---|---|---|---|
| 制御変数 | なし(満杯で落とす) | 平均キュー長 | キュー滞留時間(遅延) |
| 設定パラメータ | バッファ上限のみ | min/max/max_p の手調整 | ほぼ無調整(目標遅延のみ) |
| バースト吸収 | 可(ただし満杯時に弱い) | やや苦手 | 得意(一時キューは許容) |
| 速度非依存 | なし | なし(速度依存) | あり(時間基準なので不変) |
CoDel の内部動作
CoDel(Controlled Delay, RFC 8289)は、各パケットがデキューされる瞬間に滞留時間を計測し、それが一定以上に居座り続けるかどうかだけを見ます。パラメータは実質 2 つで、リンク速度に依存しません。
target(目標遅延): 既定 5ms。許容するキュー遅延の下限。interval(観測窓): 既定 100ms。おおむね最悪 RTT に相当する時間。
アルゴリズムの骨子はこうです。デキュー時の滞留時間が target 未満なら、キューは健全とみなしドロップしません。滞留時間が target 以上の状態が interval の間継続したら、ドロップ状態に入り 1 個落とします。ドロップ状態の間は、落とすたびに次のドロップまでの間隔を詰めていきます。
# CoDel デキュー時の擬似コード(要点)
sojourn = now - packet.enqueue_time
if sojourn < target or queue_bytes < MTU:
dropping = false # キューは健全。一時バーストは許す
else:
if not dropping:
dropping = true
count = 1
drop_next = now + interval / sqrt(count)
while dropping and now >= drop_next:
drop(packet); packet = dequeue()
count += 1
drop_next = drop_next + interval / sqrt(count)
核心は interval / sqrt(count) というドロップ間隔の縮め方です。輻輳が続くほど count が増え、次のドロップまでの間隔が逆数平方根で短くなります。これは TCP のスループットが損失率 p の平方根に反比例する(1/sqrt(p) 則)ことと整合します。スループットを線形に下げるには、ドロップ頻度を平方根のペースで上げる必要があるからです。逆にキューが捌けて滞留時間が target を下回れば dropping を解除し、次に輻輳が再発したときは前回の count を引き継いで素早く反応します(控えめにデクリメントして再利用)。
CoDel が見るのは「遅延 5ms が 100ms 続いたか」という時間の量だけです。リンクが 1Mbps でも 1Gbps でも、深刻なキューの定義は「掃けない滞留時間」で変わりません。速度やバッファサイズに依存しないため、RED のような環境ごとの再調整が要りません。
FQ-CoDel — フロー分離との合わせ技
CoDel 単体は 1 本のキューを制御しますが、実運用で広く使われるのは FQ-CoDel(Flow Queueing + CoDel, RFC 8290)です。これは入ってきたパケットを 5-タプルのハッシュで多数のサブキュー(既定 1024 本)に分け、各サブキューを CoDel で個別に管理したうえで、サブキュー間を**改良版 DRR(Deficit Round Robin)**で公平に取り出します。
効果は二重です。第一に、各フローが独立した CoDel インスタンスを持つため、1 本の大量転送(エレファントフロー)が自分のキューだけを埋め、他のフローの遅延を巻き込みません。第二に、新規・低レートのフロー(DNS 問い合わせ、対話的な ACK、Web の最初の数パケット)を優先する仕組みを持ち、これらが大量転送の後ろで待たされません。結果として、ダウンロード中でも音声通話や SSH の応答性が保たれます。FQ-CoDel はホームルータや Linux の既定キューイング規律として事実上の標準になっています。
PIE — 比例積分による遅延制御
PIE(Proportional Integral controller Enhanced, RFC 8033)は、CoDel と同じくキュー遅延を制御目標にしますが、手法が制御理論的です。CoDel がデキュー時に滞留時間を直接測るのに対し、PIE は定期的(既定 15ms ごと)に現在のキュー遅延を推定し、目標遅延 target(既定 15ms)との誤差からドロップ確率 p を更新します。
# PIE のドロップ確率更新(周期 Tupdate ごと)
err = cur_delay - target
p += alpha * err + beta * (cur_delay - old_delay)
# └比例項(P) └微分的な傾向項(I相当)
old_delay = cur_delay
alpha は現在の遅延誤差に比例して p を動かす項(比例制御)、beta は遅延の変化の向きを見て先回りする項です。遅延が目標より大きく、しかも増え続けているなら p を強めに上げ、目標に近づいて減少傾向なら緩める。これにより遅延を target 付近へ滑らかに収束させます。キュー遅延の推定は、現在のキュー長 Q をデキュー実測レートで割る(Q / drain_rate)リトルの法則的な近似で行います。エンキュー時に確率 p でドロップするため、デキュー経路の計算負荷が軽く、ハードウェア実装(DOCSIS ケーブルモデムなど)に向くのが PIE が採用された理由です。
| 項目 | CoDel | PIE |
|---|---|---|
| 制御の起点 | デキュー時の実測滞留時間 | 周期的なキュー遅延の推定 |
| 落とす場所 | デキュー時 | エンキュー時(確率 p) |
| 制御理論 | 逆数平方根のドロップ間隔 | 比例+傾向(PI 制御) |
| 目標遅延の既定 | 5ms / 窓100ms | 15ms / 更新15ms |
| 主な採用先 | Linux・FQ-CoDel・ホームルータ | DOCSIS・一部キャリア機器 |
ECN との連携と実務上の含意
AQM は「落とす」代わりに ECN(明示的輻輳通知) でパケットに輻輳マークを付けられます。落とさずに減速シグナルだけ送れるため、再送遅延を避けつつキューを浅く保てます。CoDel・PIE とも、ドロップする代わりに ECN マークする変種を持ちます。
実務上の急所は、AQM がエンド間の輻輳制御と役割分担している点です。AQM はボトルネックのキューを浅く保ち、損失や遅延のシグナルを早く送信側へ返します。送信側はそれを受けて減速します。BBR の帯域・RTT推定モデル のように損失を待たず帯域と RTT を直接推定する方式は、深いバッファ自体を埋めにくくする別アプローチで、AQM と補完関係にあります。輻輳制御方式の全体像は TCP輻輳制御の系譜 を、遅延とスループットの基礎関係は 帯域・レイテンシ・スループット を参照してください。
「過大バッファが原因なら小さくすればよい」は誤りです。バッファを削るとバースト吸収力が落ち、ロスとスループット低下を招きます。AQM の要点はサイズを削ることではなく、必要なバースト吸収(一時キュー)を許しつつ、定常キュー(滞留時間の肥大)だけを早く崩すことにあります。
「RED と CoDel の決定的な違いは何か」に即答できること。答えは「RED は環境依存のキュー長を見るため調整が破綻したが、CoDel はリンク速度に依存しない滞留時間(遅延)を見るため実質無調整で動く」。あわせて、CoDel のドロップ間隔が interval / sqrt(count) と逆数平方根で詰まるのは、TCP スループットが損失率の平方根に反比例することへの整合だ、という点も頻出です。
ネットワーク Article
バッファブロートと AQM(CoDel・FQ-CoDel・PIE)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
バッファブロート
比較で見る軸
難易度: advanced / カテゴリ: ネットワーク / タグ数: 5
導入後に効く点
原因はキュー長で輻輳を判断する古い設計。AQM はキュー長ではなく「滞留時間」を見て早めに落とす。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- ネットワーク
- タグ数
- 5
判断チェックリスト
- 自社の用途が「バッファブロート / AQM」に近いか確認する。
- 強みである「バッファブロートとは過大なバッファにパケットが滞留し、ロスは起きないのに遅延(とジッタ)が肥大する現象。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。