- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理今ここ
- DB保存
前回まで、無言からは故障と遅延を区別できないことを見ました。区別できないなら、もう一度送る。そのとき何が起きるかを層ごとに見ます。
まず結論
TCP の再送が安全なのは「もう一度送る」からではありません。送るデータが一意なシーケンス番号に結び付いていて、受信側が「これは前に見た番号だ」と判別できるからです。
HTTP の POST にはこの結び付けがありません。同じ「もう一度送る」が、層が変わると重複という副作用に変わります。
パケットが落ちる回線で大きなデータを1本の TCP 接続で送り、カーネルが何度も再送したとします。受信側のアプリが読み取るデータはどうなるでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
再送が安全なのは、送り直すからではない
第23週「届いたことを確かめる — ACKと再送、そして規定と実装のズレ」で、ACK が返らないとき送信側は何も分からない、だから一律にもう一度送る、と学びました。ここで一歩踏み込みます。もう一度送って大丈夫なのはなぜか。
RFC 9293 §3.4 は、設計の根本としてこう述べています。
A fundamental notion in the design is that every octet of data sent over a TCP connection has a sequence number.
続けて確認応答は累積的だと述べ、この仕組みの効果を書きます。
This mechanism allows for straightforward duplicate detection in the presence of retransmission.
重複が来ないとは書いていません。むしろ §3.8 は Duplicate segments may arrive due to network or TCP retransmission. と、重複が届くことを前提にしています。安全なのは届いた重複を受信側が既出だと言えるからです。
§3.4.3(The TCP Quiet Time Concept)は前提を名指ししています。
The duplicate detection and sequencing algorithm in TCP relies on the unique binding of segment data to sequence space
データと番号の一意な結び付け。崩れるとどうなるかも書かれています。causing confusion at the receiver as to which data is new and which is old — どちらが新しいか受信側が言えなくなる、と。
- ・安全性を作るのは再送という行為ではなく、識別できる番号のほう
- ・番号は接続のシーケンス空間に属する。空間が変われば結び付けも変わる
再送は起きた。しかしアプリには見えなかった
Docker のコンテナ2台(python:3.12-slim / Debian GNU/Linux 13)を同じネットワークに置き、送信側の送出側に tc qdisc ... netem loss 15% で損失を注入し、52バイト×3000行=156,000バイトを1本の TCP 接続で送りました。検証日 2026-09-17(UTC)、この構成での実行は1回です。
Tcp: RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpens PassiveOpens AttemptFails EstabResets CurrEstab InSegs OutSegs RetransSegs InErrs OutRsts InCsumErrorsTcp: 1 200 120000 -1 8 0 0 0 0 3001 2042 63 0 0 0RetransSegs (試行前): 63RetransSegs (試行後): 87→ この試行での再送: 24 セグメント (カーネルカウンタ)
送信側は24セグメントを再送しています。では受信アプリは何を読んだか。
[app] total bytes seen by application: 156000[app] distinct lines seen: 3000[app] duplicate lines seen: 0
送った通りです。156,000バイト、3000行ちょうど、重複0行。再送は確かに起きたのに、アプリからは1件も見えませんでした。 番号による吸収がカーネルとアプリの境目で効いています。
TCP の再送で受信側に重複が届かないのは、何が支えているからですか。
番号が無い層では、同じ行為が別の意味になる
一般化します。再送が安全かは、再送する側ではなく受け取る側が既出だと言えるかで決まります。 言える層では無害な冗長、言えない層では重複という副作用です。
そして、TCP の結び付けはその接続のシーケンス空間の内側に閉じています。応答が返らないので接続を張り直して同じ要求を送る操作は、新しいシーケンス空間から始まります。受信側から見れば別物です。
HTTP はこの外側です。RFC 9110 §9.2.2 は冪等なメソッドとして PUT, DELETE, and safe request methods を挙げますが、POST はその列挙に含まれません。そのうえで自動リトライをこう規定しています。
A client SHOULD NOT automatically retry a request with a non-idempotent method
TCP の側では、再送のたびに待ち時間を2倍にすることが MUST です(RFC 6298 (5.5)、後述)。一方 HTTP は非冪等メソッドの自動リトライを SHOULD NOT で止める。同じ「もう一度送る」が、片方では前提、片方では控えるべき行為です。 層が変われば意味が変わるとは、この温度差のことです。
対比例 — 再送のたびに間隔を開ける、という共通点
意味が変わるのは重複の扱いで、間隔の取り方は層をまたいで同じ発想です。RFC 6298 (5.5) は再送タイマをこう定めています。
The host MUST set RTO <- RTO * 2 ("back off the timer")
再送のたびに待ち時間を2倍にする(MUST)。第23週で既に見た規定です。相手が落ちているのか遅れているだけなのか区別できない以上、詰めて叩けば遅れているだけの相手をさらに遅くします。
つまりバックオフは層をまたいで持ち込めるが、重複の安全性は持ち込めない。リトライ処理はこの2つを分けて読んでください。
「TCP が再送を安全にしてくれるので、その上を流れる POST も再送して構わない」という説明は妥当ですか。
TCP の再送(トランスポート層)
データの1オクテットごとに番号が付いています。再送分が二重に届いても、受信側は既出の番号として扱えます。この試行でも送信側が24セグメントを再送した一方、受信アプリが読んだ行数は送った通りで重複0行でした。
観測地点 値送信側 RetransSegs 24 セグメント増加受信アプリ バイト数 156000受信アプリ 行数 3000(重複 0)
HTTP POST のリトライ(アプリケーション層)
張り直して送る要求は新しい空間から始まります。受信側に既出と判別する手掛かりが無いため、RFC 9110 は非冪等メソッドの自動リトライを SHOULD NOT で止めています。§9.2.2 が挙げる冪等なメソッドの一覧に POST は含まれません。
メソッド §9.2.2 の一覧PUT / DELETE 含まれるsafe なメソッド 含まれるPOST 含まれない
同じ「もう一度送る」なのに、片方は再送を前提に待ち時間の倍増が MUST で定められ、片方は自動リトライが SHOULD NOT で止められています。この差はどこから来ているでしょうか。
受け取った側が「これは前に見た」と言えるか、という一点で両者を並べてみてください。
この章のまとめ
- TCP の再送が安全なのは、送り直すからではなく、データが一意なシーケンス番号に結び付いているから(RFC 9293 §3.4 / §3.4.3)。重複は実際に届く(§3.8)。
- 実測(netem 15%損失、1回の試行):再送24セグメントに対し、受信アプリは 156,000バイト・3000行・重複0行。起きたが見えなかった。
- この吸収は1本の接続の内側に閉じている。張り直して送る HTTP の要求はその外側にある。
- §9.2.2 が冪等なメソッドとして挙げる一覧に POST は無く、非冪等メソッドの自動リトライは SHOULD NOT。TCP が再送を前提に間隔を MUST で定めるのと、向きが逆になる。
- 間隔の開け方(RFC 6298 (5.5)、MUST)は層をまたいで持ち込めるが、重複の安全性は持ち込めない。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
RFC 9293 §3.4.3 が、重複検出の前提として挙げているものはどれですか。
用語集(4語)
- シーケンス番号
- TCP が送信データの1オクテットごとに割り当てる番号。受信側はこれで、届いたデータが既出かを判別する。
- RetransSegs
- Linux が /proc/net/snmp に出す TCP 統計の1つ。再送セグメント数の累計で、キャプチャとは別系統の観測手段。
- RTO (Retransmission Timeout)
- ACK を待つ時間。過ぎると再送する。RFC 6298 (5.5) は再送のたびに2倍にすること(MUST)を定めている。
- netem
- Linux の tc に含まれる、遅延や損失を人工的に注入する機能。損失は乱数で、同じ設定でも試行ごとに変わる。