再送制御の内部(RTO・ファストリトランスミット・SACK)
TCPが「いつ・どれを」再送するかの判断が腑に落ちる。RTOの算出からファストリトランスミット、SACKまで、信頼性を支える仕組みを数式とシーケンスで押さえます。
- 1.RTOはRTTの平滑化平均SRTTと変動RTTVARから算出し、Karnのアルゴリズムで再送パケットの曖昧なRTTを計測から除外する。
- 2.重複ACKが3つ届くと、RTOの満了を待たずに失われた1セグメントを即座に再送するのがファストリトランスミット。
- 3.SACKは受信側が「飛び飛びに届いた範囲」を通知でき、複数欠落でも無駄な再送を避けて必要な穴だけを埋められる。
再送制御が解く問題
TCP はパケット損失や順序入れ替えのあるネットワーク上で、バイトストリームの信頼性を保証します。その中核が再送です。送信側は送ったデータを ACK(確認応答)で消し込み、ACK が返らないセグメントは失われたとみなして送り直します。問題は「いつ失われたと判断するか」です。早すぎれば届いているデータを無駄に再送し(spurious retransmission)、遅すぎればスループットが落ちます。
TCP はこの判断に 2 系統の機構を持ちます。ひとつは時間切れで再送するタイマ駆動(RTO)、もうひとつは ACK の挙動から損失を推測するACK 駆動(ファストリトランスミット)です。前者は最後の砦、後者は素早い回復を担います。前提となるコネクションと ACK の枠組みは TCPの状態遷移とコネクション確立・切断の内部 を参照してください。
RTT の計測と RTO の算出
RTO(Retransmission Timeout)は「この時間 ACK が来なければ損失」とみなすしきい値です。固定値では使えません。LAN なら数ミリ秒、衛星回線なら数百ミリ秒と RTT が桁で違うからです。そこで RTT を実測し、その平滑化平均と変動幅から RTO を動的に決めます。これが Jacobson/Karels のアルゴリズム(1988)で、現在は RFC 6298 が標準です。
各 RTT 標本 R が得られるたびに、平滑化 RTT(SRTT)と RTT 変動(RTTVAR)を指数移動平均で更新します。
RTTVAR ← (1 − beta) · RTTVAR + beta · |SRTT − R|
SRTT ← (1 − alpha) · SRTT + alpha · R
RTO ← SRTT + max(G, K · RTTVAR)
標準では alpha = 1/8、beta = 1/4、K = 4、G はクロック粒度です。最初の標本 R を得たときは SRTT = R、RTTVAR = R/2 で初期化します。
ここで重要なのは、RTO が SRTT に変動の 4 倍を上乗せしている点です。平均だけでは RTT のばらつきを吸収できません。RTTVAR を加えることで、回線が不安定なほど RTO を保守的に(長く)取り、誤った早期再送を防ぎます。
初期の TCP は RTO を平均 RTT の定数倍で決めていましたが、負荷が高く RTT のばらつきが大きい状況で破綻しました。Jacobson は「平均が同じでも、ばらつきが大きいほど次の RTT を外す確率が高い」点に着目し、変動量 RTTVAR を独立に追跡して RTO に加える設計にしました。これが輻輳崩壊(congestion collapse)を防ぐ鍵のひとつになりました。
実装上の注意点が 2 つあります。ひとつは RTO の下限で、RFC 6298 は 1 秒以上を推奨します(実装では 200ms 程度に下げることも多い)。もうひとつは指数バックオフです。再送した後さらに ACK が来なければ、RTO ← RTO · 2 と倍々に伸ばします。これは輻輳が続いている可能性が高い状況で、再送を畳みかけて事態を悪化させないためです。
Karn のアルゴリズム:曖昧な RTT を捨てる
RTT 計測には厄介な落とし穴があります。あるセグメントを再送した後に ACK が返ったとき、その ACK が元の送信に対するものか再送に対するものか区別できません。これを再送の曖昧性といいます。もし誤って計測すると、RTT を実際の倍に見積もったり半分に見積もったりして、SRTT が壊れます。
Karn のアルゴリズム(1987)は単純かつ確実な解を与えます。
- 再送されたセグメントの RTT 標本は採用しない。 曖昧なものは捨て、再送していないセグメントの ACK だけで SRTT/RTTVAR を更新する。
- ただし再送が起きている間も RTO はバックオフした値を維持する。RTT 計測を止めても、タイマ自体は指数バックオフで伸ばし続ける。
2 番目の条項が要点です。標本を捨てるだけだと、損失が続く間 SRTT が更新されず RTO が古いまま小さい値で固まり、再送の嵐になりかねません。バックオフを併用することで、計測を止めても RTO は安全側に伸び続けます。
RFC 7323 の TCP タイムスタンプオプションを使うと、各セグメントに送信時刻を載せ、ACK がそれをエコーバックします。受信した ACK のエコー値と現在時刻の差で RTT を直接測れるため、再送の曖昧性が原理的に発生せず、Karn の「標本を捨てる」制約が不要になります。長距離・高速回線では PAWS(巻き戻りシーケンス保護)と合わせてほぼ必須のオプションです。
ファストリトランスミット:RTO を待たない
RTO は最後の砦ですが、待つには長すぎます。1 つのセグメントが落ちただけで秒オーダーの空白が生じては困ります。そこで重複 ACKを損失の手掛かりに使います。
TCP の ACK は累積確認応答(cumulative ACK)です。受信側は「次に期待するシーケンス番号」を返します。途中のセグメントが欠けたまま後続が届くと、受信側は同じ ACK 番号を繰り返し返します。これが重複 ACK です。送信側は重複 ACK が 3 つ(=同じ ACK が計 4 回)届いた時点で、当該セグメントは失われたと判断し、RTO 満了を待たず即座に再送します。
送信側 受信側
│── Seg1 (seq=1000) ────────────▶│ ack=2000
│── Seg2 (seq=2000) ─────× │(Seg2 ロスト)
│── Seg3 (seq=3000) ────────────▶│ ack=2000 ← 重複ACK 1
│── Seg4 (seq=4000) ────────────▶│ ack=2000 ← 重複ACK 2
│── Seg5 (seq=5000) ────────────▶│ ack=2000 ← 重複ACK 3
│ │
│◀──────────── ack=2000 ×3 ───────│
│── Seg2 再送 (seq=2000) ───────▶│ ack=6000 ← 穴が埋まり一気に確認
なぜ 3 つなのか。1〜2 個の重複 ACK は、単なる順序入れ替え(reordering)でも発生します。経路が変わって後発パケットが先着すれば、損失していなくても重複 ACK が出ます。3 つというしきい値は、偶発的な並べ替えと真の損失を区別するための経験的な妥協点です。少なすぎれば並べ替えで誤再送し、多すぎれば回復が遅れます。
ファストリトランスミットは輻輳制御と一体で動きます。Reno ではこの後ファストリカバリに入り、cwnd を半分に落として再送 1 個分だけ素早く回復します。RTO による再送が cwnd を 1 まで叩き落とすのに対し、ファストリトランスミット経由なら被害が小さい。この差が両者の系譜を分けます。詳細は TCP輻輳制御の系譜(Tahoe→Reno→CUBIC→BBR) を参照してください。
累積 ACK は「どこまで連続して届いたか」しか伝えられません。1 つの RTT で複数のセグメントが落ちると、再送できるのは累積 ACK が指す先頭の穴 1 個だけ。次の穴は次の RTT まで分からず、回復が穴の数だけ直列化します。これが古典的 Reno の弱点で、SACK が解決する問題です。
SACK:飛び飛びに届いた範囲を伝える
SACK(Selective Acknowledgment、RFC 2018)は、累積 ACK の限界を破ります。受信側が「連続して届いた範囲」だけでなく、累積点より先で飛び飛びに届いた範囲を TCP オプションで明示的に通知します。各ブロックは [左端, 右端) の形で、TCP オプション領域(40 バイト)の制約から最大 4 個、タイムスタンプオプション併用時は 3 個まで載せられます。
受信状況: [1000-2000] 欠落 [3000-5000] 欠落 [6000-7000]
└ 累積ACK └ SACKブロック1 └ SACKブロック2
ACKオプション:
ack = 2000 ← 累積:2000まで連続受領
SACK: {3000-5000}, {6000-7000} ← 累積より先の受領済み範囲
送信側はこれを受け取ると、穴の位置を正確に把握できます。上の例なら、再送すべきは 2000-3000 と 5000-6000 の 2 区間だけだと一目で分かり、既に届いている 3000-5000 や 6000-7000 を無駄に送りません。各セグメントには SACKed(選択確認済み)の印を付けて管理します(scoreboard と呼ぶ実装が多い)。
累積 ACK のみの方式と SACK の違いを整理します。
| 観点 | 累積ACK(SACKなし) | SACK |
|---|---|---|
| 受信側が伝える情報 | 次に期待するseqの1点 | 累積点+飛び地の受領範囲 |
| 1RTTで複数欠落 | 先頭1個ずつしか再送できず直列化 | 全ての穴を同時に特定し並行再送 |
| 届いた後続の扱い | 穴より先は送信側から見えない | SACKedとして再送対象から除外 |
| 無駄な再送 | 全部送り直す実装もありうる | 穴の区間だけをピンポイント再送 |
| 有効化 | 常時 | SYNでSACK-Permittedを交換した場合 |
SACK は SYN セグメントで SACK-Permitted オプションを双方が交換したときのみ有効になります。さらに DSACK(Duplicate-SACK、RFC 2883)という拡張があり、受信側が「既に持っているデータをもう一度受け取った」ことを通知できます。これにより送信側は、自分が不要な再送をしたこと(=早すぎる RTO や偽の重複 ACK だった)を事後に検知し、輻輳ウィンドウを過剰に絞った分を補正できます。
RTO の式 RTO = SRTT + 4·RTTVAR と、平均だけでなく変動を測る理由を言えること。「重複 ACK は 3 つで再送、しきい値が 3 なのは並べ替えと損失を区別するため」。「Karn は再送セグメントの RTT 標本を捨て、RTO はバックオフを維持する」。「SACK は飛び地の受領範囲を伝え、複数欠落の並行回復を可能にする」——この 4 点が頻出です。
全体の設計思想
再送制御は 3 層の安全網として理解すると見通しが良くなります。最速層がファストリトランスミット(ACK 駆動・1RTT 程度で回復)、最後の砦が RTO(タイマ駆動・秒オーダー)、そして両者の精度を支えるのが RTT 計測(Jacobson/Karels と Karn)と SACK です。損失が軽微なら ACK 駆動でほぼ吸収し、ACK そのものが返らない深刻な状況でだけ RTO が発動します。
これらは輻輳制御と密接に連動します。再送の検知方法(重複 ACK か RTO か)によって cwnd の落とし方が変わり、スループットを左右するからです。エンド間の信頼性とトランスポート全体像は TCP と UDP の違い、損失と帯域の関係は 帯域・レイテンシ・スループット を合わせて読むと、再送制御がプロトコル全体のどこに効くかが立体的に見えてきます。
ネットワーク Article
再送制御の内部(RTO・ファストリトランスミット・SACK)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
TCP
比較で見る軸
難易度: advanced / カテゴリ: ネットワーク / タグ数: 5
導入後に効く点
重複ACKが3つ届くと、RTOの満了を待たずに失われた1セグメントを即座に再送するのがファストリトランスミット。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- ネットワーク
- タグ数
- 5
判断チェックリスト
- 自社の用途が「TCP / 再送制御」に近いか確認する。
- 強みである「RTOはRTTの平滑化平均SRTTと変動RTTVARから算出し、Karnのアルゴリズムで再送パケットの曖昧なRTTを計測から除外する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。