TCP輻輳制御の系譜(Tahoe→Reno→CUBIC→BBR)
なぜLinuxの既定がCUBICで、YouTubeがBBRを使うのか。輻輳制御の進化を一本の系譜でたどれば、各アルゴリズムの設計意図と向き不向きが腑に落ちます。
- 1.Tahoe/Renoは「パケットロス=輻輳」と見なすAIMD系。Renoは高速リカバリでロス1個ごとの大崩れを防いだ。
- 2.CUBICはRTTに依存しない3次関数の窓制御で、高帯域・長距離(大BDP)でも窓を素早く埋める。Linux既定。
- 3.BBRはロスでなく帯域とRTTを実測するモデルベース。バッファ肥大下でも低遅延・高スループットを狙う。
輻輳制御が解こうとした問題
TCPは確実にデータを届けますが、ネットワークの容量は有限です。送信側が無制限に送れば経路上のルータのキューが溢れ、パケットが捨てられ、再送が再送を呼んで全体が崩壊します。これが1986年に実際に起きた 輻輳崩壊(congestion collapse) です。
輻輳制御は「相手の受信能力」を守る フロー制御 とは別物です。フロー制御は受信ウィンドウ(rwnd)で相手のバッファを守り、輻輳制御は 輻輳ウィンドウ(cwnd) で経路全体を守ります。実際に送出できる量は両者の小さい方、min(cwnd, rwnd) で決まります。以降の主役はこの cwnd です。
第1世代:Tahoe(1988)— AIMDとロス=輻輳の前提
Van Jacobsonが導入したTahoeは、現在まで続く2つの柱を確立しました。
- スロースタート:接続開始時、ACKを1つ受けるごとに cwnd を1ずつ増やす。RTTごとに cwnd が倍増する指数増加で、未知の経路容量を素早く探る。
- 輻輳回避(AIMD):閾値 ssthresh を超えたら、RTTごとに cwnd を1だけ増やす線形増加に切り替える。
Tahoeの根本前提は 「パケットロスは輻輳のサインである」 という点です。ロスを検知すると ssthresh を現在の cwnd の半分にし、cwnd を1に戻してスロースタートからやり直します。安全ですが、ロス1個ごとに振り出しに戻るため効率が悪い。
# AIMD の心臓部
# AI: Additive Increase(線形に増やす)
# MD: Multiplicative Decrease(乗算で一気に減らす)
ロスなし: cwnd += 1 (1 RTT ごと)
ロス検知: cwnd = cwnd / 2
第2世代:Reno(1990)— 高速リカバリで大崩れを防ぐ
Renoの貢献は 高速再送・高速リカバリ です。Tahoeはロスのたびに cwnd=1 へ戻しましたが、Renoは「重複ACKが3つ届いた」場合を 軽い損失 と解釈します。データは流れ続けている証拠だからです。
- 3つの重複ACK → 該当セグメントを即再送(タイムアウトを待たない)。cwnd を半分にして輻輳回避へ戻る(cwnd=1にはしない)。
- タイムアウト → 深刻とみなし、Tahoe同様 cwnd=1 からやり直す。
これにより単発ロスでのスループット落ち込みが大幅に改善しました。一方で 1 RTT 内に複数パケットを失うと回復が苦手 という弱点があり、後継の NewReno がこれを部分ACKの扱いで補強し、長らく標準実装となりました。
Reno系は「次に欲しい番号」しか伝えられず、複数欠落の把握が困難でした。SACK(Selective ACK / RFC 2018) は受信済みの飛び飛びの範囲を通知でき、再送を的確にします。多くの近代アルゴリズムはSACKを前提に損失回復を行います。
なぜAIMD系では足りなくなったか
問題は回線が太く・長くなったことです。帯域遅延積(BDP=帯域×RTT) が大きい経路では、窓を満たすのに必要な cwnd が巨大になります。Renoの線形増加(1 RTTで+1)では、半減したあと元に戻すのに数千RTTを要し、太い回線を全く使い切れません。詳しくは /network/bandwidth-latency/ のBDPの議論を参照してください。
Renoの平均スループットはおおむね「ロス率の平方根に反比例」します。つまり高速・低ロスな経路ほど、わずかなロスでも本来の帯域に届きません。RTTが長いほど増加が遅く不利になる RTT公平性 の問題も抱えます。
第3世代:CUBIC(2008)— RTT非依存の窓制御
LinuxがBICを経て既定に採用したCUBICは、cwnd を 時間(前回ロスからの経過秒数)の3次関数 で増やします。Renoが「RTTごとに+1」だったのに対し、CUBICは 実時間 を基準にするため、RTTの長短に左右されにくくなります。
# CUBIC の窓成長(概念)
# W_max = 直近でロスした時点の cwnd
# t = ロスからの経過時間, K = 復帰までの目安時間
W(t) = C * (t - K)^3 + W_max
# 復帰直後: W_max 付近へ急速接近 → 安定域でゆっくり → さらに探索で再加速
挙動は3段階です。ロス直後は W_max 付近まで一気に(凹型で)戻り、W_max 近傍では 平らに(慎重に) なり、問題なければ 再び凸型で加速 して新たな上限を探ります。これにより大BDP経路でも窓を素早く埋めつつ、安定点付近では穏やかに振る舞います。
| 観点 | Reno / NewReno | CUBIC |
|---|---|---|
| 窓増加の基準 | RTT回数(+1 / RTT) | 実時間の3次関数 |
| RTT公平性 | 短RTTが有利に偏る | RTT非依存に近い |
| 大BDP経路 | 窓を埋めきれず低速 | 素早く窓を満たせる |
| 輻輳の判断 | パケットロス | パケットロス |
| 主な採用 | 歴史的標準 | Linux既定 / 広く普及 |
ただしCUBICも ロスを輻輳のシグナルとする 点はReno系と同じです。ここに、現代の落とし穴が潜みます。
バッファブロート問題 — ロス基準の死角
近年のルータやモデムは大容量バッファを積みます。すると輻輳しても すぐにはパケットを捨てず、キューに溜め込みます。ロス基準のCUBICはロスが出るまで送り続けるため、巨大なキューを満たしてしまい、パケットは落ちないのに遅延だけが激増 します。これが バッファブロート(bufferbloat) です。スループットは出ているのに体感が重い、典型的な症状です。
第4世代:BBR(2016)— モデルベースという転換
GoogleのBBR(Bottleneck Bandwidth and Round-trip propagation time)は前提を覆します。ロスを輻輳の合図にしない。代わりに2つの量を継続的に 実測 し、経路のモデルを作ります。
- BtlBw:ボトルネックの最大帯域(実測した配送レートの最大値)
- RTprop:伝播遅延(実測した最小RTT。キュー遅延を含まない素のRTT)
理想の動作点は、この2つの積、すなわち BDPちょうどのデータを飛ばしている状態 です。それより少なければ帯域を余らせ、多ければキューに溜まるだけ(=遅延増)。BBRはペーシングで送出レートをBtlBwに合わせ、内部状態を循環させて両者を測り直し続けます。
# BBR の核となる目標
inflight ≈ BtlBw × RTprop (= BDP に張り付ける)
送出レート = BtlBw を基準にペーシング
# Probe帯域でレートを一時的に上げて BtlBw を更新
# Probe RTT で送出を絞り RTprop を測り直す
初代BBRはロスを無視するため、CUBICなどロス基準の競合フローを押しのけて帯域を奪うことがあり、また浅いバッファでは過剰なロスを生む場合がありました。これらは BBRv2 / v3 でロスやECNのシグナルを取り込む形で改良が進んでいます。「BBR=常に最適」と決め打ちしないこと。
系譜と分岐の整理
年代と「何を輻輳のシグナルにしたか」で分岐を捉えると、進化の筋が見えます。
- 1988 Tahoe:スロースタート+AIMDを確立。ロス=輻輳。ロスで cwnd=1。
- 1990 Reno:高速リカバリを追加。単発ロスでの大崩れを回避。→ NewReno が複数欠落を補強。
- (分岐・遅延ベース)Vegas(1994):RTT増加を輻輳の予兆とみなす先駆。普及は限定的だが思想はBBRに通じる。
- 2008 CUBIC:BIC→CUBICへ。実時間3次関数でRTT非依存・大BDP対応。Linux既定。依然ロス基準。
- 2016 BBR:帯域・RTPropの実測によるモデルベースへ転換。ロス非依存。→ v2/v3で公平性を改善中。
| 世代 | 代表 | 輻輳のシグナル | 解いた主な問題 |
|---|---|---|---|
| 第1 | Tahoe | パケットロス | 輻輳崩壊の防止(AIMD確立) |
| 第2 | Reno / NewReno | パケットロス | 単発ロスでの過剰な落ち込み |
| 分岐 | Vegas | RTT(遅延)の増加 | ロスより前に輻輳を察知 |
| 第3 | CUBIC | パケットロス | 大BDP経路での窓充填の遅さ |
| 第4 | BBR | 帯域とRTPropの実測 | バッファブロート(過剰遅延) |
実務での確認と選択
現在のLinuxでは利用可能なアルゴリズムを確認し、必要に応じて切り替えられます。HTTP/3(QUIC)はこの輻輳制御を カーネルではなくユーザー空間 に持つため、アプリ側でBBRを使う構成も一般的です(/network/http-versions/)。
# 利用可能なアルゴリズムを一覧
sysctl net.ipv4.tcp_available_congestion_control
# 現在の既定(多くのディストリで cubic)
sysctl net.ipv4.tcp_congestion_control
# BBR へ切り替え(要 tcp_bbr モジュール、fq qdisc 推奨)
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
選択の指針はシンプルです。ロスが主に輻輳起因の素直な経路 なら枯れたCUBICで十分。大容量バッファや無線で遅延が膨らみやすい長距離経路(動画配信など)では、遅延を抑えつつ帯域を使い切るBBRが効きます。いずれもTCPの土台(/network/tcp-udp/)の上で、cwndという1つの変数をどう動かすかという一貫した問いの答えだと理解すると、系譜全体が一本につながります。
ネットワーク Article
TCP輻輳制御の系譜(Tahoe→Reno→CUBIC→BBR)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
TCP
比較で見る軸
難易度: advanced / カテゴリ: ネットワーク / タグ数: 5
導入後に効く点
CUBICはRTTに依存しない3次関数の窓制御で、高帯域・長距離(大BDP)でも窓を素早く埋める。Linux既定。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- ネットワーク
- タグ数
- 5
判断チェックリスト
- 自社の用途が「TCP / 輻輳制御」に近いか確認する。
- 強みである「Tahoe/Renoは「パケットロス=輻輳」と見なすAIMD系。Renoは高速リカバリでロス1個ごとの大崩れを防いだ。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。