HTTP/1.1の「Connection: keep-alive」が教える、TCPセッションという名の「重い代償」
Webの黎明期、HTTP/0.9や1.0の世界はあまりにも刹那的だった。クライアントがリクエストを投げれば、サーバーはレスポンスを返し、即座にTCPのFINパケットが飛ぶ。それはまるで、挨拶もそこそこにドアを閉めて去っていく旅人のような関係性だ。
しかし、現代のWebサイトは1ページを開くだけで数百の静的リソースを要求する。もし、そのすべてでTCPの3ウェイ・ハンドシェイク(SYN, SYN-ACK, ACK)を繰り返し、さらにはTLSのネゴシエーションまでやり直していたらどうなるか。我々は、光速に近い速度で移動するパケットを、律儀に「接続・切断」の儀式で堰き止めていることになる。
ここで登場するのが `Connection: keep-alive` だ。今回は、この古典的でありながら今なおWebの基礎体力であるヘッダーが、カーネルレベルで何を引き起こしているのかを紐解いていく。
—
1. パケットの深淵:TCP接続を「再利用」するということ
`Connection: keep-alive` が有効なとき、サーバーはレスポンス送信後もソケットを閉じない。これにより、次のリクエストを待機する「Persistent Connection(持続的接続)」が確立される。
ここでインフラエンジニアが注目すべきは、TCPのSlow Start(スロースタート)の回避である。TCPは接続直後、輻輳を避けるために送信ウィンドウサイズを意図的に小さく保つ。接続を使い回すことで、ウィンドウサイズが十分に拡大した状態の経路を維持できる。これは、RTT(ラウンドトリップタイム)が支配的なモバイル回線において、体感速度を劇的に改善する決定打となる。
しかし、闇雲に接続を保持すれば良いわけではない。サーバー側で `Keep-Alive Timeout` を短くしすぎれば接続効率は落ち、逆に長すぎれば「ゾンビ化した接続」がメモリを食いつぶす。
—
2. TLSハンドシェイクの「重力」と最適化
現代の通信において、HTTPは必ずTLSとペアである。`keep-alive` の真価は、TLSハンドシェイクのコストを「無効化」できる点にある。
TLS 1.2以前では、暗号スイートのネゴシエーションや証明書の交換に多大なRTTを要した。TLS 1.3になってハンドシェイクは高速化されたが、それでも「何も通信しない」ことに勝るパフォーマンスはない。
Linuxカーネルチューニングの勘所
もしあなたがNginxやHAProxyをチューニングする立場なら、以下のsysctlパラメーターは必須のチェック項目だ。
TCP接続のタイムアウトを最適化し、ゾンビ接続を素早く回収する
サーバーの負荷が高い環境では、FIN_WAIT状態の回転率が重要
sysctl -w net.ipv4.tcp_fin_timeout=15
TCPバッファの自動調整。keep-aliveで保持する接続のメモリ消費を抑える
読み取りバッファの最小/デフォルト/最大値を適切に設定する
sysctl -w net.ipv4.tcp_rmem=’4096 87380 4194304′
TCPキープアライブ・プローブの調整(アプリケーション層のkeep-aliveとは別物)
接続が生きているか確認する間隔を短縮
sysctl -w net.ipv4.tcp_keepalive_time=600
—
3. 脆弱性とセキュリティ:ヘッダーが引き起こす災厄
`keep-alive` を語る上で避けて通れないのが、「HTTP Request Smuggling(HTTPリクエスト・スマグリング)」だ。
この攻撃は、フロントエンド(リバースプロキシ)とバックエンドで「どこまでが1つのリクエストか」という解釈のズレが生じたときに発生する。`Content-Length` と `Transfer-Encoding` ヘッダーを悪用し、持続的接続の中に不正なリクエストをねじ込む手法だ。
これを防ぐための鉄則はシンプルである。
- ヘッダーの厳格な検証: フロントエンドとバックエンドのプロトコルバージョンを統一し、ヘッダーの曖昧さを許容しない。
- プロトコルのアップグレード: 可能な限りHTTP/2以降を採用すること。HTTP/2はバイナリフレーミング層により、`keep-alive` のような曖昧な制御に頼ることなく、ストリーム単位での多重化を強制する。
—
4. 最後に:なぜ今、我々はこれを理解すべきか
HTTP/3(QUIC)が普及し、UDPベースの通信が主流になりつつある現代において、TCPの `keep-alive` は「レガシーな技術」に見えるかもしれない。
しかし、実務において我々が直面するトラブルの多くは、依然としてこのTCP/TLSのレイヤーで起きている。接続の終了間際に発生する `RST` パケットの競合、カーネルバッファの枯渇、そして持続的接続の多重化によって引き起こされるポート番号の枯渇。
`Connection: keep-alive` を単なる設定値として見るか、それとも「数万人のユーザーが共有する限られた帯域とカーネルリソースの調停者」として見るか。その視点の差が、大規模なトラフィックを捌くインフラアーキテクトの矜持となるはずだ。
パケットは嘘をつかない。あなたのサーバーが流しているのは、ただのデータか、それとも最適化された意思か。今一度、`tcpdump` を走らせて、その接続がどう終わっているのかを確認してみてほしい。そこには、教科書には書かれていないリアルな挙動が刻まれている。
コメント