Dev Study
← ネットワークの仕組み
レッスン 10 / 13·約 18 分·第2期 ネットワークの仕組み

届いたことを確かめる — ACKと再送、そして規定と実装のズレ

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択今ここ
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理
  7. DB保存

第2期の後半、端点どうしの会話を扱う区間の3回目です。第21週でポートによる多重化、第22週で接続の確立を見ました。接続がつながった後、そこを流れるデータが「確かに届いた」とどう分かるのかが今回の主題です。次の第24週では、届くと分かったうえで「どれだけ送ってよいか」を扱います。

まず結論

ネットワークには「送った」ことを保証する仕組みがありません。TCPが信頼できるのは、送った側が相手からの確認応答(ACK)を待ち、返ってこなければもう一度送るからです。届いたことを知る方法は、相手に言ってもらうことしかありません。

そして本レッスンの山場は、この再送の待ち時間(RTO)です。RFC 6298 は最小値を「1秒に切り上げ」と規定していますが、Linuxの実装値は200msです。規定を読んだだけでは実際の挙動が分からない——その典型例がここにあります。

? 読む前に予測

TCPが再送を始めるまでの待ち時間(RTO)の最小値について、規定と実装の関係として正しいのはどれでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

「送った」と「届いた」は別のこと

第22週で接続がつながりました。つながった以上、そこへ流し込んだデータは相手に届く——そう考えたくなりますが、そうはなりません。

ネットワークには、データが失われないことを保証する仕組みがありません。途中のルータが混んでいれば捨てられますし、線の途中で化ければ破棄されます。しかも捨てた側は、誰にも謝りません。落ちたことは黙って起きます。

では送信側は、どうやって「届いた」と知るのでしょうか。

方法はひとつしかありません。相手に言ってもらうことです。 これが**確認応答(ACK)**です。受信側が「ここまで受け取った」と返し、送信側はそれを見て初めて、自分のデータが相手の手元にあると判断します。

逆に言えば、ACKが返ってこないうちは何も分かりません。データが落ちたのか、ACKのほうが落ちたのか、相手が遅れているだけなのか、送信側からは区別がつきません。区別がつかないので、TCPは一律に同じ対応を取ります——もう一度送る。これが再送です。

送信側から見えているものと、見えていないもの
送信側がデータを送る
ここから先は見えない
無事に届いたACKが返る
データが落ちたACKは返らない
ACKが落ちたACKは返らない
送信側にできる判断
ACKが来た = 届いた / 来ない = 分からない
  • ・後ろ2つは送信側から区別できない。どちらも「ACKが返らない」としか見えない
  • ・区別できないので、対応は一律に「もう一度送る」になる
✓ ここまでの確認

送信側が「データが届いた」と判断できる唯一の根拠は何ですか。

具体実物

では、どれだけ待ってから再送するのか

ここで具体的な問いが立ちます。ACKが返らないとき、どれだけ待ってから再送すべきでしょうか。

短すぎれば、まだ途中にいるだけのデータを重ねて送ることになり、混んでいる網をさらに混ませます。長すぎれば、落ちたと分かるまでに時間を浪費します。この待ち時間がRTO (Retransmission Timeout) で、その計算方法を規定しているのが RFC 6298 です。

RFC 6298 が定めている値を並べます。

RFC 6298 が規定するRTOの値と、要件の強さ表
項目 規定内容 要件語 出典
初期値 1秒に設定する SHOULD §2.1
最小値 1秒未満なら1秒に切り上げる SHOULD §2.4
最大値 60秒以上の上限を置いてよい MAY §2.5
再送時 RTO <- RTO * 2 とする MUST §5.5

注目してほしいのは要件語の列です。同じ「規定」でも強さが違います。MUSTは従わなければならない要件、SHOULDは原則従うべきだが理由があれば逸脱してよい要件、MAYは選択肢の提示です。

最小値の根拠も RFC 6298 §2.4 に書かれています。「TCPを保守的に保つには大きな最小RTOが必要だと研究が示唆している」。初期値が1秒である根拠は Appendix A にあり、大規模な観測で接続の97.5%超がRTT 1秒未満だったことが挙げられています。規定には理由が添えられているわけです。

抽象原理

ここが本題 — 規定を読んでも、実際の挙動は分からない

さて、以上がRFCの規定です。ではLinuxで動いているTCPの最小RTOは何秒でしょうか。

200msです。1秒ではありません。

Linuxカーネルの include/net/tcp.h には、次の定義が置かれています。

Linux カーネル include/net/tcp.h(抜粋、v6.15 以降。v6.14 以前は TCP_RTO_MAX を (120*HZ) と直書き。/* = 200ms */ は本教材の注釈)設定ファイル
#define TCP_RTO_MAX_SEC 120
#define TCP_RTO_MIN ((unsigned)(HZ / 5)) /* = 200ms */
#define TCP_TIMEOUT_INIT ((unsigned)(1*HZ)) /* RFC6298 2.1 initial RTO value */

3行を規定と突き合わせます。

RFC 6298 の規定 vs Linux の実装値表
項目 RFC 6298 の規定 Linux 実装値 評価
初期値 1秒 (§2.1) 1秒 一致
最小値 1秒に切り上げ (§2.4) 200ms 食い違う
最大値 60秒以上 (§2.5) 120秒 規定を満たす

初期値は一致しています。しかもソース中のコメントが RFC6298 2.1 initial RTO value と、参照している節まで明示しています。この実装はRFCを読んで書かれているわけです。

それでも最小値は食い違います。規定は1秒、実装は200ms。5分の1です。

そして、この節の見出しに戻ります。規定を読んだだけでは、目の前のマシンがどう動くかは分かりません。 RFC 6298 を最初から最後まで読んでも、自分のLinuxサーバが200msで再送を始めることは書いてありません。それはカーネルのソースにしか書かれていません。

逆も同じです。TCP_RTO_MIN の値だけを見ても、なぜ200msなのか、それが逸脱なのかどうかは分かりません。規定と実装の両方を見て、初めて事実の全体が見える——これが本レッスンで持ち帰ってほしい姿勢です。

⇄ 2つを見比べる
具体例 A

TCPの再送タイマ — ACKが返るまで送信バッファを手放さない

送信側はデータを送り出した後も、そのデータを送信バッファに保持し続けます。手放してよいのは累積ACKで確定した範囲だけです。RTOが満了してもACKが返らなければ、保持していた同じデータをもう一度送ります。「送信処理が完了した」ことは、相手が持っていることの根拠になりません。

ACKが返らないときの送信側の動き追跡ログ
送信 データを送る。送信バッファは保持したまま
待機 RTO のあいだ ACK を待つ
満了 ACK が来ない → 保持していた同じデータを再送
バックオフ RTO <- RTO * 2 (RFC 6298 §5.5、MUST)
確定 累積ACK が届いて初めて、その範囲をバッファから解放
具体例 B

ディスク書き込みの fsync

write が成功を返してもデータはまだページキャッシュにあるだけで、SSDが保持したとは限りません。確かに保持されたと判断するには fsync を呼び、その戻りを待つ必要があります。write の成功は「自分の手元の処理が終わった」ことしか意味しません。

書き込みが確定するまで追跡ログ
write() 成功が返る。ただしページキャッシュに入っただけ
待機 まだ保持されたとは言えない状態が続く
fsync() デバイスへの反映を要求し、戻りを待つ
確定 fsync が返って初めて「保持された」と判断できる
障害時 fsync 前に落ちれば、書いたはずの内容は消えている

TCPの再送タイマと、ディスク書き込みの fsync。扱っている対象はまったく違うこの2つに、どんな共通の構造があるでしょうか。そしてその構造は、何を防いでいるでしょうか。

「自分の処理が終わった時点」と「相手が持ったと確認できた時点」が、それぞれどこにあるかを並べて比べてください。

具体実物

タイマを待たずに気付く方法 — 高速再送

RTOの話を続けます。仮に最小RTOが200msだとしても、タイマの満了を待つのは遅いという問題は残ります。1つのセグメントが落ちただけで、次の送信が数百ミリ秒止まるからです。

ここで使えるのが、受信側が返してくるACKそのものです。

TCPのACKは累積型です。Acknowledgement Number フィールドは「この番号の手前まではすべて連続して受け取った」ことを意味します。したがって途中の1つが欠けると、その先がいくら届いても、受信側は同じACK番号を返し続けるしかありません。

送信側から見ると、同じACKが何度も返ってくる。これが重複ACKで、「先は届いているのに手前が欠けている」という信号になります。

重複ACKが損失を知らせる仕組み
セグメント1が届く連続して受け取れた○ 次を期待するACKを返す
セグメント2が落ちる受信側には届かない× 何も返せない
セグメント3・4・5が届く先は届いたが手前が欠けている× 同じACK番号を返し続ける = 重複ACK
重複ACKが3回RFC 5681 §3.2 の指標○ RTO満了を待たずに再送する(高速再送)
↓
タイマの満了を待つより早く損失に気付ける。きっかけは3回(2回ではない)
  • ・RFC 5681 §3.2: 「3回の重複ACKの到着を、セグメントが失われた指標として用いる」
  • ・「重複ACK」の基本の定義は §2 の5条件すべてを満たすもの(SACK を使う実装は SACK 情報で判定してもよい)。同じACK番号が見えただけでは該当しない

きっかけの回数は3回です。「2回」という説明が広く出回っていますが、RFC 5681 §3.2 が書いているのは3回です。

なお「重複ACK」という語も、見た目で決まるものではありません。RFC 5681 §2 は5つの条件を挙げており、未確認のデータが残っていること、そのACKがデータを運んでいないこと、SYNとFINがともにoffであること、ACK番号がこれまでの最大値と等しいこと、広告ウィンドウが直前のACKと等しいこと——このすべてを満たすものが重複ACKです。なお同節は別法として、SACK を使う実装が SACK 情報をもとに重複ACKを判定することも認めています。

抽象原理

累積ACKの限界と、SACKが足すもの(そして足さないもの)

累積ACKには構造的な限界があります。伝えられるのが「ここまでは連続して受け取った」という一点だけだからです。その先で何が届き何が欠けているかは表現できません。

そのため、1つのウィンドウの中で複数のセグメントが失われると厄介なことになります。RFC 2018 の表現を借りると、送信側は「1往復時間あたり単一の失われたパケットについてしか学習できない」のです。2つ落ちていれば、1つ目を直してから次の往復でようやく2つ目が見えます。

これを解くのが SACK (Selective Acknowledgement) です。受信側が「飛び地としてここも持っている」と追加で伝えることで、送信側は RFC 2018 の言う「欠けているデータセグメントだけ」を再送できます。

ただしここに、2つの外せない注意があります。どちらもRFC 2018 の原文に明記されています。

この2点は繋がっています。SACKが助言的だからこそ、確定の役割は累積ACKに残されているのです。「SACKがあれば累積ACKは不要」と考えると、どこまでが確定でどこからが暫定なのかの区別が消えてしまいます。

✓ ここまでの確認

SACK(選択的確認応答)によって、送信側はどこまで受信済みかを細かく知ることができます。では、SACKで「受け取った」と報告された範囲のデータを、送信側は再送用のバッファから消してよいでしょうか。

この章のまとめ

  • ネットワークは損失を防がない。送信側が「届いた」と判断できる根拠は、受信側から返るACKだけ。ACKが返らない理由(データ損失/ACK損失/遅延)は送信側から区別できないので、対応は一律に再送になる。
  • 再送までの待ち時間がRTO。RFC 6298 は初期値1秒(§2.1、SHOULD)、最小値は1秒への切り上げ(§2.4、SHOULD)、最大値は60秒以上の上限を置いてよい(§2.5、MAY)、再送のたびに2倍(§5.5、MUST)と規定している。
  • 山場。Linuxの TCP_RTO_MIN は HZ / 5 = 200ms で、規定の「1秒に切り上げ」と食い違う。ただし要件語がSHOULDであり、RFC 6298 §2.4 自身がより小さい最小RTOの可能性を認めているため、違反ではなく正当な逸脱。初期値1秒と最大値120秒は規定と整合する。
  • 規定を読んだだけでは実際の挙動は分からず、実装だけを見てもそれが逸脱かどうかは分からない。 両方を見て、食い違っていたら要件語まで戻る。
  • 高速再送のきっかけは重複ACK 3回(RFC 5681 §3.2)。2回ではない。「重複ACK」は基本的に §2 の5条件をすべて満たすものを指す(SACK 情報で判定する別法もある)。
  • 累積ACKだけでは「1往復あたり1つの損失しか学習できない」。SACKは欠けたセグメントだけの再送を可能にするが、助言的なので送信側はSACK済みデータのバッファを解放してはならず、また累積ACKの意味も変えない。
  • ACKを待つ構造は、ディスク書き込みの fsync と同型。どちらも「自分の処理が成功した」ことを「相手が保持した」証拠にしない。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 5正解: 0

「TCPの最小RTOは200msである」という説明の、最も正確な評価はどれですか。

用語集(5語)
ACK(確認応答)
受信側が送信側へ返す「ここまで受け取った」という通知。TCPのACKは累積型で、Acknowledgement Number フィールドは「この番号の手前まではすべて連続して受け取った」ことを意味する。送信側が「届いた」と判断できる唯一の根拠。
RTO (Retransmission Timeout)
ACKが返らないときに再送を始めるまでの待ち時間。RFC 6298 が計算方法を規定している。初期値は1秒(§2.1)、最小値は1秒への切り上げ(§2.4、SHOULD)、最大値は60秒以上の上限を置いてよい(§2.5、MAY)。再送のたびに2倍にする(§5.5、MUST)。
TCP_RTO_MIN
Linuxカーネルの `include/net/tcp.h` に定義された最小RTO。値は `HZ / 5` で200ms。RFC 6298 §2.4 の「1秒に切り上げ」という規定から意図的に逸脱している。要件語がSHOULDのため規格違反ではない。
高速再送 (fast retransmit)
RTOの満了を待たずに再送する仕組み。RFC 5681 §3.2 が規定しており、3回の重複ACKの到着を、セグメントが失われた指標として用いる。タイマの満了を待つより早く損失に気付ける。
SACK (Selective Acknowledgement)
RFC 2018 が定める、飛び地として受け取った範囲を送信側に伝えるオプション。送信側は欠けているデータセグメントだけを再送できる。ただし助言的(advisory)であり、受信側は報告済みのデータを後から破棄してよい。累積ACKの意味は変えない。