TCPの輻輳制御
この記事では、「TCPの輻輳制御とは何か」「具体的にどういったアルゴリズムでウインドウが制御されているのか」をざっくり説明します。
はじめに: HTTP/3時代にTCPの輻輳制御・・・?
今後インターネットでは HTTP/3 の利用が急速に増えると思われます。これまでの HTTP/2 と比べると、アンダーレイが TCP から QUIC(UDP)に変わることが大きな違いの一つです。
もしかすると、「HTTP/3 の時代にTCPの輻輳制御を学ぶことに意味があるの?」と思う方もいらっしゃるかもしれません。まあ、そもそも、インターネットに流れている TCP は HTTP だけではありませんから、引き続き必要ですよね、というのが最初の回答です。でも、仮に HTTP に限って考えるとしても、以下の理由で引き続き重要です。
- HTTP/3 のアンダーレイである QUIC の輻輳制御は、TCP の輻輳制御と同じアルゴリズムが使われている
- HTTP/3 は単体で動かない(HTTP/3 の実現のためには HTTP/2 または HTTP/1.1 が必須)
HTTP/3 時代になっても、TCP の輻輳制御は引き続き重要な存在 ということです。
ウィンドウサイズ
さて、重要性が分かったところで本題へ進みましょう。まずはウインドウサイズについて説明します。

元来、TCP は高信頼性を確保するために考えられたプロトコルです。そのため、受信側は、各セグメントを確実に受信した旨を伝えるため、セグメント単位で ACK(Acknowledge)を返信する決まりがあります。この決まりにより、送信側はACK応答を受信することで、データが確実に送信できたことが確認できます。ここで、セグメントとは、TCPのデータ送信を行う最小単位のことで、TCP のヘッダを1つだけ含むものです。上図の濃い紫色の線それぞれがセグメントです。
初期の TCP では、上図右のように、1セグメントだけデータを送り、その ACK が応答されるまで次のセグメントを送らない仕組みでした。信頼性が重要で、ACK が必要だとしても、このやり方はあまりに非効率です。そこで、「ACK が返って来る前に複数のセグメントを同時に送って良いよ」というルールを作ることにしました。ここで、 一度に送れるセグメントの数をウインドウサイズ といい、「ネットワークの状況を鑑み、ウインドウサイズをどう賢く制御するか」を輻輳制御といいます。ここで、輻輳とは、通信ネットワークが混雑していることを指す技術用語です。輻輳が続くと通信できなくなってしまいます。
ちなみに、輻輳制御の話をするときに、ウインドウの値を一気に大きくすることを「アグレッシブ」と表現します。また、対義語として、少しずつウインドウの値を大きくすることを「慎重」と表現することもあります。
フロー制御と輻輳制御
輻輳制御と混同しがちなものとしてフロー制御があります。いずれもTCPをより効率的に運用するための技術という点では共通ですが、何に備えているか(=何のキャパシティを超えないように制御するか)という点が異なります。

- フロー制御
- 受信側のキャパシティを超えないようにする仕組み
- 受信側が送信側に受信ウインドウサイズを伝え、送信側に従ってもらう
- 輻輳制御
- 送受間のネットワークのキャパシティを超えないようにする仕組み
- 送信側がネットワークの状態を推定し、送信側が自律的に輻輳ウインドウサイズを制御する
いずれの制御も、一度に送信する量を変えるのは(もちろん)送信側です。しかしながら、フロー制御は、受信側ホストのコンピューターの性能を超えて一度にデータを送信されないように制御します。この受信側の性能のことを受信バッファといい、受信バッファの残量を都度伝える仕組み(「スライディングウインドウ」といいます)により、最新の受信バッファ残量を送信側に伝えています。
フロー制御で制御するウインドウサイズを受信ウインドウサイズ、輻輳制御で制御するウインドウサイズを輻輳ウインドウサイズと呼んで区別します。以後、この記事では輻輳制御による輻輳ウインドウサイズについてだけ解説します。
輻輳制御アルゴリズムの分類
現在よく使われている輻輳制御アルゴリズムは1つだけではありません。この章でまず、輻輳制御アルゴリズムの分類を説明します。
- Loss-based
- パケットの廃棄をもとにネットワークの状況を推測
- Delay-based
- パケットの遅延量をもとにネットワークの状況を推測
ネットワークの状況を完全に知ることはできませんから、何らかの指標をつかって輻輳やその予兆を推測する必要があります。現代のアルゴリズムは、上で示すように、大きく廃棄で推測する方法と遅延で推測する方法の2つに分類できます(組み合わせたものを Hybrid と呼ぶ場合もあります)。
今のトレンドは Delay-based です。そして最も注目を集めているアルゴリズムは、Delay-based の代表格といえる BBR だと断言して異論は出ないでしょう。しかし、Delay-base がいかなる場合も優れているとは断言できません。BBR は現代のインターネットにおいてはおそらく最適に近いものだと思いますが、ネットワークの特性に応じた様々なアルゴリズムが研究されており年々進化していることは忘れてはなりません。
(余談)遅延と輻輳って関係あるの?
余談です。長いし、本題の理解に必須ではないので、読み飛ばして頂いても構いません。
ネットワークが輻輳している状態というのは、ネットワークのキャパシティを超えてパケットの廃棄が発生している状態です。すなわち、パケットの廃棄を基準にする Loss-based なアルゴリズムは、輻輳を輻輳の定義に即して判断するアルゴリズムということです。とても直感的ですね。でも、Delay-based は、そうではありません。どうして遅延をネットワークの輻輳判定に利用するのでしょうか? 確かに遅延は短い方がよさそうですが、そうだとしても、遅延と輻輳に何の関係があるのでしょう?
理解のために、日本〜アメリカ間のTCPコネクションと、日本同士のTCPコネクションを比較してみましょう。明らかに前者の方がパケットの遅延が大きいはずです。でも、例えば日本国内の通信だけ混雑していて、日本とアメリカの間の方がむしろ混雑していない場合もありえませんか?この仮定の下では、遅延の大きな日本とアメリカのTCPコネクションの方が、輻輳していないといえますよね。
この疑問を理解するためには、ネットワークの輻輳直前に何が起きるか正確に理解する必要があります。昨今のネットワーク機器は、輻輳時でも可能な限りパケット廃棄を抑える工夫がされています。具体的には、送信先の回線の伝送容量を超えそうになった場合、機器でバッファリングします。つまり、ルータが一時的にパケットを溜め、回線に余裕ができたタイミングで送信するのです。その結果、パケットの廃棄が起こる前には、遅延が増大します[1]。このネットワーク機器の特性から、遅延の増加=ネットワーク輻輳の前兆と捉えることができるのです。
もう少し深く考えると、パケットの遅延量は概ね「伝送による遅延+ルータのバッファリングの遅延」で表されますが、輻輳制御において重要なのは「ルータのバッファリングの遅延」のみだということです。先の例で日本とアメリカの話をしましたが、その差は「伝送による遅延」ですから、本来あまり輻輳制御とは関係がなく、除外して考えるべきです。ただ実際には、TCPの送信者・受信者が遅延の要因を確実に識別するのは困難です。そこで、「伝送による遅延」が頻繁に増減しにくい特性を考慮して、遅延の増加を契機に制御するなどの工夫が行われています。
輻輳制御アルゴリズム
Tahoe
最も基本的で伝統的なアルゴリズムが Tahoe です。現代において使う理由はありませんが、輻輳制御の概念・用語・課題を理解する上で有用ですので説明します。

こういうグラフがよく出てきますので読み方に慣れましょう。横軸が時間で、縦軸がウインドウサイズです。紫線のグラフが、ウインドウサイズの時間変化を表しています。
最初はスロースタートです。つまり、最初はウインドウサイズを1で送信、すなわち1セグメントだけ送信して ACK 受信を待つ動作をします。次はウインドウサイズを2倍、その次は4倍、8倍・・・と二次関数に従って大きくしていきます。すると、そのうちネットワークの限界に達し、輻輳が発生しますね。Tahoe は Loss-based のアルゴリズムですから、TCPセグメントに廃棄が発生した場合、すなわち ACK 応答がなかったり Duplicate ACK[2] が応答されたりした場合に輻輳として判断します。
Tahoe は、最初の輻輳を迎えた後、ウインドウサイズを1まで戻します。 更に、それ以降は少し動作が変わり、「輻輳が発生したウィンドウサイズ÷2」の値(図中点線で示す値)までは1回目と同じ二次関数ですが、それ以降は一次関数でウインドウサイズを増やします。輻輳発生後は繰り返し輻輳を起こさないように慎重になるんですね。ちなみに、この動作フェーズを輻輳回避フェーズといいます。
Reno/New Reno

Tahoe はとても慎重なアルゴリズムで、転送効率がよくありませんでした。その課題を解決するために考えられたアルゴリズムがRenoです。
Tahoe は、輻輳が発生したときにウインドウサイズを1まで戻してしまいます。一方 Reno は、輻輳が発生したとき、ウインドウサイズを「輻輳が発生したウィンドウサイズ÷2」の値まで戻します。 結果としてすぐに輻輳回避フェーズが始まります(これを高速リカバリといいます)。Tahoe の転送効率が悪い最大の理由は輻輳後に毎回発生するスロースタートでした。Reno は、初回以外スロースタートが発生しないアルゴリズムにすることで、転送効率の大幅な改善を達成しました。
また、Reno の改善版である New Reno というアルゴリズムもあり、パケットがバースト的に廃棄されてしまう場合の挙動が改善されています。現代では、単に「Reno」とだけ呼んだ場合でも、こちらの「New Reno」を指していることが多いです。
CUBIC

昔は Reno/New Reno がとても良く動いていたのですが、インターネットの高帯域に伴い、Reno/New Reno の高速リカバリですら効率があまり良くない状況となりました。そこで、より速くリカバリを行うアルゴリズムが検討され、紆余曲折[3]あった後、CUBIC が登場しました。CUBIC は、現在において Loss-based の主流といえる輻輳制御アルゴリズムのひとつです。Linux カーネル標準の TCP 輻輳制御アルゴリズムとしても採用されています。
CUBIC は、最初の輻輳を検知するところまでは Reno と同じですが、その後の輻輳回避フェーズが大きく異なります。前回の輻輳を検知したウインドウサイズまでは慎重にウインドウサイズを増加させ、過ぎた後は、とてもアグレッシブにウインドウサイズを増加させます。 言い換えると、図のように、前回の輻輳を検知したウインドウサイズを極とした三次関数のようにウインドウサイズを制御します。
BBR

先ほども触れたとおり、いま最も注目されているアルゴリズムは BBR です。Google が開発した制御方法で、QUIC でも採用されることが多いです。元々発表された BBR を BBRv1、改良が加えられ2026年現在標準化が進んでいる BBR を BBRv3[4] と呼んで区別します。
BBR は Delay-based なアルゴリズムです。遅延量とスループット(帯域)を監視 することを念頭に、理解を深めていきます。
まず、Delay-based と Loss-based の違いを詳しく理解するため、ウインドウサイズが小さい順に、以下の3段階を定義します。[5]。
- 遅延していない かつ ウインドウサイズを増やせばそれに従ってスループット(帯域)が増加する
- 遅延が増加しはじめる かつ ウインドウサイズを増やしてもスループット(帯域)が増加せず、頭打ちする
- パケットの廃棄が発生する
スロースタートでウインドウサイズを増やしているフェーズであれば、フェーズは 1->2->3 の順に遷移します。仮に Loss-based なアルゴリズムであれば、フェーズ 2 から 3 に遷移した時点、つまりパケットの廃棄が発生した時点で輻輳と判断し、輻輳回避フェーズに入ります。一方、純粋な Delay-based なアルゴリズムは、フェーズ 1 から 2 になった時点で輻輳回避フェーズに入ります。Delay-based では、輻輳の予兆が見られたら、早めに輻輳を回避する、とても慎重なアルゴリズムというわけですね。
さて、BBR は純粋な Delay-based アルゴリズムとは少し違い、この2つの間をとったような動作をします。具体的には、フェーズ 2の間、慎重な動作をしたりアグレッシブな動作をしたりすることはありません。現状維持を試みます。 上図を見ると、「帯域上限(1と2の閾値)〜輻輳直前(2と3の閾値)の間をさぐる」 動作をしている、というのが直感的にわかると思います。実際にはここまでシンプルではありませんが、概要はこんな感じです。
実は、Delay-based なアルゴリズムは、長年研究がされていたものの、これまで主流になることはありませんでした。しかし、BBR の最大の特徴といえる「現状維持を試みる」動作のおかげで、主流なアルゴリズムの仲間入りを果たしました。2026年現在、BBRv1 は既に Linux カーネルにも採用されており、希望すれば CUBIC ではなく BBRv1 を利用できるようになっています。また、Google の主要なサービスではすでに BBRv3 を採用しているそうです。
(余談)なぜDelay-basedはこれまで主流にならなかったの?
Delay-basedの欠点と主流にならなかった理由です。読み飛ばしてもかまいません。
端的にいうと、Delay-basedは慎重すぎるからです。
そもそも、ネットワークの仕組み上、パケットの廃棄よりも先にパケットの遅延が発生します[6]。ということは、すでに Loss-based な輻輳制御アルゴリズムが大半を占めるインターネット環境において、より早くウインドウサイズを小さくする Delay-based な輻輳制御アルゴリズムを導入すると、どうなるでしょう?Delay-based なアルゴリズムは、Loss-based なアルゴリズムよりも先にウインドウサイズを小さくしてしまいますよね。つまり、Delay-based は遠慮しすぎなんです。
この世に Loss-based なアルゴリズムが一切無ければ、おそらく純粋なDelay-based なアルゴリズム(代表例としてVegasがあります)が最適だったのと思います。しかし、残念ながらこの先も、そんなことは起こりえませんね。つまり、New Reno や CUBIC などの Loss-based なアルゴリズムが世間に浸透してしまっている以上、頑張って考えた Delay-based の新しいアルゴリズムの方がスピードが出なかった、ということです。これでは、頑張って新しいアルゴリズムを導入するモチベーションが沸きませんよね。
まとめ
- 輻輳制御とは、「TCPのセグメントを一度にどれだけ送るか」を表すウインドウサイズをネットワークの都合に応じて制御すること
- 具体的なアルゴリズムとして、パケット破棄を基に制御する方法と、パケット遅延を基に制御する方法の2つがある
- 最近は前者はCUBIC・後者はBBRが主流で、最新のHTTP/3でもそれらのアルゴリズムが使われる見込み
- 初期のアルゴリズムは、初期(スロースタートフェーズ)や破棄・遅延の発生直後の挙動(輻輳回避フェーズ)が問題で、ウインドウサイズをなかなか大きくできずに転送効率が悪いという欠点があった
- CUBICやBBRはそれらの欠点を克服し、2026年現在、効率的かつ現在のインターネット環境に耐えうるアルゴリズムとなっており、Linux カーネルでも採用されている
なお、筆者は、BBRの詳しい動作を勉強中です。特にv3は、動作は分かっても意図を理解するのが難しすぎます。もう少し理解が深まれば、BBRについての記事も書いてみたいと思っています。それでは。
参考文献
- http://www.net.c.dendai.ac.jp/~yutaro/
- https://datatracker.ietf.org/meeting/104/materials/slides-104-iccrg-an-update-on-bbr-00
-
あくまでレイヤー1の話ですから、TCPに限った話ではないことに注意ください。 ↩︎
-
ACKが途中で破棄されてしまった場合に発生します。Duplicated ACK は、受信者側からみて、ACK を送ったはずのTCPセグメントが再度送られてきた場合に応答されます。 ↩︎
-
主に公平性の観点で様々な議論がありました。すでに Reno による輻輳制御が行われている環境に対し、Reno と異なる新しい制御を導入すると、Reno/New Renoがほぼ通信できなくなったり、逆に新しいプロトコルがほぼ通信できなくなるケースがありました。そのため、"相性のよい"輻輳制御が必要でした。 ↩︎
-
BBRv2は存在しないと理解して差し支えありません。BBRv2のある課題を解決したものをv3と呼んでおり、ベースのアルゴリズムは同じです。 ↩︎
-
BBRの「フェーズ」は、Startup / Drain / ProbeBW / ProbeRTT ですが、その話とは関係ありません! この記事では、ウインドウサイズに対する遅延やスループット(帯域)の状態のことをだけを論じます(もっと言うと、わかりやすさのために正確性を犠牲にして、必要な説明をはしょっています)。 ↩︎
-
理由は「(余談)遅延と輻輳って関係あるの?」をご参照ください ↩︎
Discussion