- ネットワーク
- 技術 💻
TCPの落とし穴8選|TIME_WAIT・40ms遅延の正体
TCPって、「信頼できる通信をしてくれる縁の下」として、中身を見ずに使いますよね。
でも、メッセージがくっついて届く、ポートが枯れる、謎の40ms遅延——ソケットを触った人が必ず出会う怪現象は、全部TCPの仕様が説明してくれます。今回は、その種明かしを8つ集めました。1つでも知らなかったら儲けものです。
① TCPに、「メッセージの区切り」はありません
いちばん大事な真実から始めます。
TCPが運ぶのは、メッセージではなく「切れ目のないバイトの川」です。
送信側がsendを2回呼んでも、受信側のrecv 1回でまとめて届くことがあります。逆に、send 1回分が2回に分割されて届くこともある。送った回数と受け取る回数は、一致する保証がありません。
「たまにメッセージが壊れる」自作プロトコルの原因は、ほぼこれです。だからHTTPは長さや区切りをヘッダで宣言し、あらゆるプロトコルが自前の「区切りルール」を持っているんです。
② 接続を「切った側」に、TIME_WAITが残ります
コネクションを閉じたのに、ソケットがしばらく居座る——TIME_WAITです。
これはバグではなく仕様で、先に切断した側が、遅れて届くパケットを受け止めるために数十秒(Linuxでは60秒)待つ仕組みです。
問題は、短命な接続を大量に張って切る側(クライアントやプロキシ)で、TIME_WAITが数万個溜まってポートが枯渇すること。「負荷試験でエラーが出るのに、サーバーは暇そう」の定番犯人です。
根本対策は、接続を使い回す(コネクションプーリング/keep-alive)こと。張っては切る設計そのものを見直すんです。
③ 謎の40ms遅延は、「気配り」の衝突です
小さいデータのやり取りで、たまに一定の遅延が乗る。これ、有名な相性問題です。
| どちら側 | 節約機能 | 気配りの中身 |
|---|---|---|
| 送信側 | Nagleアルゴリズム | 小さいパケットを溜めて、まとめて送る |
| 受信側 | 遅延ACK | ACKを少し待って、まとめて返す |
両方の気配りが噛み合うと、お互いに相手待ちになり、タイマー切れ(数十ms)まで固まるんです。Linuxの遅延ACKの最短タイマーが40msなので、「40ms」という数字でよく知られています。
対策は、送信側の節約をオフにするTCP_NODELAY。低遅延が命のプロトコルがこれを指定するのは、この衝突を避けるためなんです。
④ 「接続」は、途中の機器には見えていません
TCPコネクションという「線」が張られているイメージ、実は幻想です。
接続という状態は、両端のOSの中にしかありません。途中のルーターは、独立したパケットを転送しているだけです。
だから、片側だけが「接続は生きている」と信じ続ける状態(ハーフオープン)が起きます。相手が電源断で消えても、こちらが何か送るまで気づけない。
「切断を検知できると思っていたのに、何時間も気づかなかった」の理由がこれです。検知したいなら、次の⑤の仕組みが要るんです。
⑤ keep-aliveの既定は、「2時間」です
④の対策としてTCPには生存確認(keep-alive)がありますが、驚くのはその既定値です。
多くのOSで、最初の確認まで2時間。つまり既定のままでは、相手の死をリアルタイムには検知できません。しかもソケットオプションで明示的に有効化しないと、そもそも動きません。
だから実務では、アプリケーション層で自前のハートビートを打つのが定石です。WebSocketのping/pongやDB接続の死活確認は、TCP任せにしない、という設計判断なんです。
⑥ 1個のパケロスが、後続全部を道連れにします
TCPの「順序保証」には、代償があります。
パケットが1つ落ちると、その後ろのデータが届いていても、アプリには渡されません。抜けた部分の再送が来るまで、全員で待つ。ヘッドオブラインブロッキングです。
1本のTCPに複数のリクエストを相乗りさせるほど、この巻き添えは大きくなります。
HTTP/3がTCPを捨ててUDP上のQUICに移った最大の理由が、これです。順序保証をストリーム単位に分割して、巻き添えを止めたんですよね。
⑦ 受信が遅いと、送信は自動で止まります
TCPには、隠れた流量調整が組み込まれています。
受信側は「あとどれだけ受け取れるか(ウィンドウ)」を常に申告していて、受信アプリが読み取りをサボると、ウィンドウが0になり、送信側は完全に停止します。
これ自体は健全な仕組み(バックプレッシャ)ですが、知らないと怪奇現象に見えます。「送信側が遅い」と思って調べたら、犯人は受信側の処理詰まりだった——通信のボトルネック調査あるあるです。
遅いのはどちら側か。ウィンドウを見れば、答えが出るんです。
⑧ 接続確立だけで、1往復かかります
TCPは通信を始める前に、3回の挨拶(3ウェイハンドシェイク)をします。つまりデータを1バイトも送る前に、必ず1往復ぶんの時間(RTT)を払うわけです。
東京〜米国西海岸なら、それだけで100ms前後。TLSの鍵交換も乗れば、さらに往復が増えます。
だから現代の高速化は、「接続を減らす・使い回す・先に張っておく」が主戦場です。コネクションプール、keep-alive、そして接続と暗号化の往復を圧縮したQUIC。
速さの敵は帯域ではなく、往復回数。ネットワーク設計の合言葉なんです。
番外編:手元で確かめられる小ネタ
TIME_WAITを数えてみる
ss -tan | grep TIME-WAIT | wc -l
負荷試験の前後で数えると、②の話が目の前で起きます。
保証されるのは「順序と到達」だけ
TCPは「送った順に、いつか届く」を保証しますが、「いつ届くか」は保証しません。遅延の上限が要る世界(音声・ゲーム)でUDPが選ばれるのは、この割り切りなんですね。
SO_LINGERという危険なつまみ
切断時の挙動を変えるオプションですが、雑に使うと相手にエラーを叩きつける動きになります。「見つけたら意図を確認する」系の設定です。
RJCでも、通信と向き合っています
RJCが携わる決済や交通のシステムは、外部接続の安定性がそのままサービス品質になる世界です。②のプーリングや⑤のハートビートは、設計書の定番項目なんですよね。
こういう仕組みの話を、Techチームで共有しながら開発しています。
よくいただくご質問
Q. まずどれから押さえるべきですか?
A. ①かなと思います。「TCPはバイトの川」の一文だけで、通信まわりの不可解なバグの半分は説明がつくんですよね。
Q. ソケットを直接触らない仕事でも、必要ですか?
A. 効きます。②⑧はHTTPクライアントの設定(プール・keep-alive)の話そのものなので、APIを呼ぶ全員に関係あるんです。
Q. 障害調査でどこから見ればいいですか?
A. ssコマンドで接続の状態(TIME_WAIT、ウィンドウ)を見るのが最短です。⑦のとおり「どちら側が詰まっているか」まで分かることが多いです。
Q. 未経験からでも、分かるようになりますか?
A. なります。TCPは「郵便を、番号付きで、確認ハガキ付きで送る」という比喩で骨格が全部説明できるんです。
「信頼できる通信」の中身を知る
今日の8つに共通していたのは、これでした。
TCPの「信頼性」は魔法ではなく、待ち時間・再送・停止という実費で買われています。その実費がどこで発生するかを知っている人だけが、怪現象を仕様として説明できる。
「TCPだから大丈夫」から「TCPだから、ここに気をつける」へ。この一歩が、通信に強い人の入口なんですよね。
みなさんが踏んだ通信の怪現象、よかったら教えてください。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。