なぜそのコネクションは切れないのか?HTTP Keep-AliveとTCPの「泥沼」を読み解く
現場でインフラを触っていると、必ず一度は「サーバーのリソースが枯渇しているのに、接続数が一向に減らない」という怪現象に遭遇する。監視ツールを見れば ESTABLISHED 状態のTCP接続がズラリ。サーバー管理者は「タイムアウトを設定したはずだ」と首をかしげる。
その原因の多くは、HTTPの Keep-Alive とTCPのタイマー設定の「噛み合わせ」の悪さにある。今日は、パケットがネットワークを駆け巡る現場の視点から、この見えない境界線をどう制御すべきか、深く掘り下げていこう。
—
1. Keep-Aliveの正体:効率化の代償
HTTP/1.1の Keep-Alive は、TCPの3ウェイ・ハンドシェイクを繰り返すコストを削減するための賢い仕組みだ。一度確立したTCP接続を使い回すことで、レイテンシを劇的に改善する。
しかし、この「使い回し」が長すぎれば、サーバーは誰も使わない「幽霊のようなコネクション」を抱え続けることになる。これを制御するのが Keep-Alive タイムアウトと Max-Keep-Alive-Requests だ。
サーバーが管理すべき2つの境界線
1. Keep-Alive Timeout: クライアントが何も送ってこないときに、サーバーが接続を切断するまでの待機時間。
2. Max-Keep-Alive-Requests: 一つの接続で処理できる最大リクエスト数。これを超えると、サーバーは Connection: close を返して強制的に切断する。
ここでの落とし穴は、「OSのTCPタイムアウト設定と、アプリケーション(Webサーバー)のタイムアウト設定は別物である」という点だ。
—
2. 通信シーケンスのリアル:FINパケットの行方
多くのエンジニアが勘違いしているのが、HTTP層でタイムアウトが発生した瞬間にTCP接続が消滅するわけではないということだ。
1. サーバー側: Keep-Alive タイムアウト到達。
2. サーバー側: HTTP層ではなく、TCP層で FIN パケットを送信。
3. クライアント側: ACK を返し、FIN を投げて 4-way handshake を完了させる。
もし、クライアントがこの FIN パケットを無視(あるいはネットワークの途中でドロップ)した場合、サーバー側には FIN_WAIT_1 や FIN_WAIT_2 といった状態のソケットが残り続ける。この「ゴミ」が重なると、ファイルディスクリプタの限界を迎え、新規接続を受け付けなくなる。これが現場でよく見る「突然のサービス停止」の正体だ。
—
3. 実践:Nginxでの適切な設定
Nginxを例に挙げよう。デフォルト値は「運用環境には長すぎる」ことが多い。
http {
# Keep-Aliveの待機時間は短めに設定する(5秒〜15秒が推奨)
keepalive_timeout 15s;
# 1つの接続で許容するリクエスト数。APIサーバーなら多めにしても良い
keepalive_requests 100;
}
ここで重要なTipsがある。ロードバランサー(ALBやELB)とバックエンドサーバーのタイムアウト設定は、必ず「ロードバランサー側を短く」設定すること。 そうしないと、ロードバランサーが接続を切った後にバックエンドが応答を返そうとして RST パケットを食らうという、無駄な通信が発生する。
—
4. デバッグの現場:クライアント側の振る舞い
開発者が書くクライアントコードでも、この挙動を意識する必要がある。例えば、Pythonの requests ライブラリでセッションを張る場合だ。
import requests
# Sessionを使うとKeep-Aliveが有効になる
session = requests.Session()
# 複数のリクエストを同じ接続で送る
for i in range(10):
# 接続が維持され、TCPハンドシェイクのオーバーヘッドが減る
response = session.get('https://api.example.com/data')
print(f"Status: {response.status_code}")
# 最後に必ず閉じる。これを忘れると接続が掴みっぱなしになる
session.close()
CLIで挙動を確認するなら、curl の -v(verbose)オプションが最強の武器だ。
# -v でHTTPヘッダーと接続状況を追う
curl -v -k https://api.example.com/
出力の中に Connection: keep-alive というヘッダーが見えれば成功している。もし Connection: close が返ってくるなら、サーバー側の keepalive_requests 上限に達している可能性がある。
—
5. まとめ:現場のエンジニアへのアドバイス
ゼロトラスト全盛の今、境界防御の考え方は「どこで接続を切るか」に集約される。
- Web APIの場合: 接続は短命に。
keepalive_timeoutは短くし、サーバーのリソースを解放せよ。 - 高負荷なフロントエンド: 接続は長めに。ただし、
Max-Keep-Alive-Requestsで強制的にローテーションさせ、メモリリークやセッション固着を防げ。 - トラブル時:
netstat -an | grep ESTABLISHEDで接続数を確認し、特定のクライアントIPから異常に多いコネクションが張られていないか確認すること。
ネットワークは生き物だ。仕様書通りの挙動だけでなく、TCPのステート遷移が今どうなっているのかを想像しながら、パケットの呼吸を感じ取ってほしい。それができれば、どんな障害も怖くはないはずだ。
コメント