【実務・中級編】HTTP/1.1のKeep-Alive接続とタイムアウト設定(Keep-Aliveヘッダー) – HTTPプロトコル・通信規格実践ガイド

TCPコネクションの「使い回し」が勝負を決める:HTTP/1.1 Keep-Aliveの真髄

ネットワークエンジニアとして現場を渡り歩いていると、システムのパフォーマンスが頭打ちになった際、必ずと言っていいほど「TCPコネクションのオーバーヘッド」が犯人として浮上します。

HTTP/1.0の時代、ブラウザは1つのオブジェクトを取得するたびにTCPの3ウェイ・ハンドシェイクを繰り返していました。今では当たり前のWebページでも、画像やCSSを合わせれば100リクエストは軽く超えます。毎回SYN/ACKをやり取りし、スロースタートでTCPウィンドウサイズが広がるのを待つ……そんな無駄な往復を繰り返していては、現代の爆速なWeb体験は実現できません。

そこで登場したのが、HTTP/1.1におけるKeep-Alive(持続的接続)です。今回は、この「接続の再利用」という概念が、インフラ設計やAPI開発においてどれほど重要なのか、現場の視点から掘り下げていきます。

—

1. Keep-Aliveの仕組みとシーケンス

HTTP/1.1では、デフォルトで`Connection: keep-alive`が有効です。これにより、最初のレスポンスが返った後もTCPコネクションを即座に切断せず、次のリクエストのために「待機状態」に入ります。

通信フローの比較

  • HTTP/1.0 (Close): [SYN]→[ACK]→[GET]→[RES]→[FIN]→[ACK](毎回発生)
  • HTTP/1.1 (Keep-Alive): [SYN]→[ACK]→[GET]→[RES]→[GET]→[RES]→…→[FIN](一度だけ)

この「FIN/ACK」の往復を数回減らすだけで、レイテンシは劇的に改善されます。特に、TLSハンドシェイクを伴うHTTPS通信において、この恩恵は計り知れません。

—

2. サーバー側のタイムアウト設定:バランスの妙技

「じゃあ、コネクションはずっと繋ぎっぱなしにすればいいのか?」というと、それはインフラ屋としては悪夢です。アイドル状態のコネクションを数千個も保持すれば、サーバーのメモリとファイルディスクリプタを食いつぶし、OSは悲鳴を上げます。

ここで重要になるのが、タイムアウト設定です。

Nginxでの設定例

Nginxでは、`keepalive_timeout`で接続保持時間を制御します。

http {
# 接続を保持する時間 (デフォルト75秒。長すぎるとメモリを圧迫する)
keepalive_timeout 65;

# 1つのコネクションで処理できる最大リクエスト数
# これを超えると強制的にコネクションをクローズし、クライアントに再接続を促す
keepalive_requests 100;
}

現場のTips:

  • `keepalive_timeout`: 接続頻度が低いAPIなら5秒〜10秒程度で十分です。長すぎると「死に体」のコネクションが溜まる原因になります。
  • `keepalive_requests`: 大量のリクエストを捌くAPIでは、メモリリーク防止のためにもこの値を適切に設定し、定期的にコネクションをリフレッシュ(FIN)させることが重要です。

—

3. 実務でのデバッグ:curlとPythonで確認する

「本当にKeep-Aliveが効いているのか?」を確かめるには、パケットキャプチャやクライアント側のヘッダーを確認するのが一番です。

curlで確認する

`-v`オプションをつけてリクエストを投げると、通信の詳細が見えます。

-v: 詳細表示
2回連続でリクエストを送ると、TCPの再利用状況がわかる
curl -v http://example.com/api/v1/data > /dev/null
curl -v http://example.com/api/v1/data > /dev/null

出力結果の中に `Re-using existing connection #0` という文字列が見えれば、Keep-Aliveは成功しています。

Python (requests) での扱い

Pythonの`requests.Session`を使うと、内部的にコネクションプールを維持してくれます。

import requests

Sessionオブジェクトを使うと自動的にコネクションがプールされる
session = requests.Session()

1回目のリクエスト: TCPハンドシェイクが発生
session.get(‘https://api.example.com/resource’)

2回目のリクエスト: 同じ接続が再利用される(パフォーマンス向上)
session.get(‘https://api.example.com/resource’)

—

4. インフラエンジニアが直面する「落とし穴」

最後に、トラブルシューティングの現場でよくある失敗談を共有します。

1. ファイアウォールによる切断:
サーバー側で「60秒タイムアウト」にしていても、途中のロードバランサーやFWが「30秒でアイドル接続を切断」している場合があります。サーバーが「まだ接続は生きている」と思ってデータを送ると、クライアントにはRST(リセット)パケットが返り、`Connection reset by peer`というエラーになります。FWのアイドルタイムアウト値は必ずサーバーの設定値より大きく設定するのが鉄則です。

2. HTTP/1.1のHead-of-Line Blocking (HoL Blocking):
Keep-Aliveは接続の再利用には効きますが、1つのコネクション内でリクエストを直列に処理するため、前のリクエストが重いと後続が詰まります。これがHTTP/2で多重化(Multiplexing)が導入された最大の理由です。

まとめ:賢いインフラ運用のために

Keep-Aliveは「効率」と「リソース」のトレードオフです。闇雲に保持時間を長くすれば良いわけではなく、クライアントのトラフィック特性に合わせてタイムアウト値をチューニングし、FWやロードバランサーとの境界条件を一致させる。この泥臭いチューニングこそが、真に堅牢なインフラを作るための近道です。

さあ、皆さんのシステムの`keepalive_timeout`は、今のトラフィックに対して最適化されていますか?まずは`curl -v`で、接続の寿命を覗いてみることから始めてみてください。

コメント

タイトルとURLをコピーしました