輻輳制御 — TCP はネットワークの混雑に譲る
経路の混み具合は誰も教えてくれない。TCP は cwnd という自前の窓を倍々で広げ、パケットロスを合図に譲る — 探りながら送る仕組みを手計算で追う。
相手は元気でも、道が混んでいる
第 4 章のフロー制御で、相手を溺れさせることはなくなりました。では、rwnd の許す限り全力で送ってよいでしょうか。答えは否です。送信側と受信側の間には、他人の通信も流れ込む共有のネットワークがあります。途中のルータの処理能力を超えた分はキューに溜まり、キューがあふれれば捨てられます。全員が全力で送れば、再送が再送を呼び、ネットワーク全体が詰まって誰のデータも流れなくなります(輻輳崩壊 — 1980 年代に実際に起きた事故です)。
厄介なのは、経路の空き容量は誰も教えてくれないことです。rwnd のような申告はありません。そこで TCP は約束その 3 の後半として、送信側が自力で経路の容量を推定する輻輳制御を持ちます。推定値はもう 1 つの窓 — 輻輳ウィンドウ(cwnd) — として持ち、実際に送ってよい量は min(rwnd, cwnd) で決まります。相手の都合と道の都合、厳しい方に合わせるのです。
cwnd の一生。倍々で駆け上がり(スロースタート)、しきい値から先は一歩ずつ(輻輳回避)、ロスで譲って登り直す。ギザギザの鋸歯は TCP が健全に限界を探り続けている姿です。
探り方 — 倍々で駆けて、一歩ずつ詰める
容量が分からない道では、少なく始めて様子を見ながら増やすしかありません。TCP の探り方は 2 段変速です。
そして経路の限界を越えるとパケットロスが起き、第 3 章の検知が働きます。ここで証拠の重さに応じて罰を変えるのが TCP の知恵です。
増やして、ぶつかって、譲って、また増やす。cwnd のグラフが描く鋸歯(のこぎり)状の波は、TCP がネットワークの容量を全員で分け合うために払い続けている挨拶のようなものです。誰かが譲らない設計だったら、インターネットは成り立っていません。
小問 5-1 — 2 つのブレーキを区別する
「誰の都合に合わせているか」で 2 つの窓を言い分けられるようにします。
Q1. フロー制御と輻輳制御の違いの説明として正しいものはどれですか。
合わせる相手が違います。フロー制御(第 4 章の rwnd)は受信側が申告してくるバッファの空き。輻輳制御(この章の cwnd)は誰も申告してくれない経路の混雑を、送信側が自力で推定した結果です。受信側は元気でもネットワークが混んでいることも、その逆もあるので、独立した 2 つのブレーキが要ります。
Q2. TCP がパケットロスを「混雑の合図」として使うのはなぜですか。
経路のルータは基本的に何も教えてくれません(明示的な通知の仕組みもありますが、常には頼れません)。しかし混雑の物理的な帰結として、あふれたキューはパケットを捨てます。だから「ロスが起きた = どこかのキューがあふれ気味」という推理が成り立つのです。ロスには電波品質など混雑以外の原因もあるため完璧な合図ではありませんが、追加の協力なしに使える最も普遍的な信号です。
Q3. 送信側がある瞬間に送ってよいデータ量(in-flight の上限)を決めるものはどれですか。
2 つのブレーキは同時に効きます。相手のバッファ(rwnd)と経路の推定容量(cwnd)、どちらか厳しい方 = min(rwnd, cwnd) が実際の上限です。第 4 章の「上限 = ウィンドウ ÷ RTT」の式のウィンドウも、正確にはこの min の値です。片方だけ大きくしても、もう片方が小さければスループットは伸びません。
実務でどう見えるか
輻輳制御は普段は透明ですが、性能問題では必ず顔を出します。
- 接続直後は誰でも遅い — どんな太い回線でも cwnd = 1 MSS 近辺から探り始めるため、短い通信は一度もトップスピードに達しません。小さなリクエストを大量に張り直すより、1 本のコネクションを使い回す(keep-alive)方が速い理由がこれです。
- ロス 1 回のコストは「再送 1 個」では済まない — cwnd が半分(または 1)に落ちるので、スループットの回復に何 RTT もかかります。ロス率 1% は「1% 遅くなる」ではなく、桁違いに遅くなることがあります。
- 速度の頭打ちには 3 つの容疑者 — rwnd(相手のバッファ)、cwnd(経路の混雑)、そして帯域そのもの。第 4 章の式に当てはめ、どの窓が min を取っているかを考えるのが切り分けの型です。
小問 5-2 — cwnd の成長を手計算する
cwnd はセグメント数(MSS 単位)で数えます。倍々と足し算、2 つの成長を計算してください。
Q4. スロースタートで cwnd = 1 MSS から送信を始めました。1 RTT ごとに cwnd が 2 倍になるとき、3 RTT 経過後の cwnd は何 MSS ですか。
1 → 2 → 4 → 8 で、3 RTT 後は 8 MSS です。「スロー」という名前に反して成長は指数的 — 1 RTT に送ったセグメントの ACK が 1 つ返るたびに cwnd を 1 増やすと、結果として毎 RTT で倍になります。10 RTT もあれば 1024 MSS に達する勢いで、空いている回線ならすぐに使い切れる速さです。
Q5. しきい値(ssthresh)を超えて輻輳回避モードに入り、cwnd = 10 MSS から 1 RTT ごとに 1 MSS ずつ増やしています。5 RTT 経過後の cwnd は何 MSS ですか。
10 + 5 = 15 MSS です。限界が近いと分かってからも倍々を続けると、超過した瞬間に大量のロスを起こします。そこで危険水域では足し算(1 RTT に +1 MSS)に切り替えて、そっと限界を探ります。遠くは倍々で駆け、限界近くは一歩ずつ — この 2 段変速が TCP の探り方です。
Q6. ロスを検知したときの TCP の反応は、検知のしかたで異なります。正しい組み合わせはどれですか。(第 3 章の 2 つの検知方法を思い出してください)
証拠の重さに罰の重さを合わせます。重複 ACK 3 回(高速再転送)は「後続は届いている = 経路は生きている」軽症のサインなので、cwnd を半分に落として走り続けます。タイムアウトは「何も返ってこない」重症のサインなので、cwnd = 1 MSS まで戻してスロースタートからやり直します。キャプチャでスループットの崖を見たら、どちらの検知が引き金だったかを疑う — これが第 7 章で使う読み方です。
この章で持ち帰ること
- フロー制御は相手の都合、輻輳制御はネットワークの都合。送ってよい量は min(rwnd, cwnd)
- 経路の容量は申告されないので、パケットロスを混雑の合図として自力で探る
- 成長は 2 段変速 — スロースタートは毎 RTT で倍々、輻輳回避は毎 RTT で +1 MSS
- 罰も 2 段階 — 重複 ACK 3 回で半分、タイムアウトで 1 に戻してやり直し。鋸歯の波は健全な譲り合いの姿
これで 3 つの約束がすべて揃いました。次章は視点をアプリ側に移します。TCP が完璧に約束を守っているのに、アプリのデータが「欠ける・くっつく」ように見える — 実務で最も多い TCP の誤解、バイトストリームとフレーミングの話です。