メインコンテンツへスキップ
  1. Posts/

[Network] TCP接続はどのように維持されるのか — TCP Keepalive、HTTP Keepalive、HikariCP

NineKoo9
著者
NineKoo9
目次

このドキュメントは疑問に思っていた点をAIエージェントとの対話を通じて整理したメモです。TCP接続がすぐに切れない理由と、TCP keepalive、HTTP keep-alive、HikariCP keepaliveの違いを整理します。

まず区別すべきこと
#

このドキュメントで「keepalive」という言葉は実際には3つの異なるものを指す。

名称レイヤー実際の意味
TCP keepaliveL4 / OSアイドル状態のTCPソケットに対してカーネルが生存確認probeを送る
HTTP keep-aliveL7 / HTTPレスポンス後にTCP接続を閉じず、次のHTTPリクエストでも再利用する
Pool keepaliveアプリケーションHikariCPのようなpool実装がidle connectionを定期的にpingする

この3つを混同すると「TCP keepalive = HikariCP keepalive = Nginx keepalive」のように見えてしまうが、実際には担当するレイヤーと目的が異なる。


TCP接続は何も送らなくても維持されるのか?
#

プロトコルの観点ではYes。

TCP接続は3-way handshakeが終わってESTABLISHED状態に入ると、データがないという理由だけで自動的には切れない。 片側がFINまたはRSTを送るか、カーネル/アプリケーション/中間機器がtimeoutポリシーで整理するときに切れる。

つまり「何もパケットを送らなければTCPが自動的に終了する」という説明は間違いだ。

しかし実務では以下の主体たちがidle connectionを整理する。

  • DBサーバー自体のidle timeout
  • プロキシ/ロードバランサーのidle timeout
  • NAT/ファイアウォールのconnection tracking timeout
  • アプリケーションまたはconnection poolの自体のライフサイクルポリシー

そのためTCPは維持できるが、実際のサービス経路全体が維持してくれるとは言えない。


実際にidle connectionを切る主体たち
#

アイドル接続を誰が切るかは環境によって異なる。一つの数値を覚えてすべての区間に当てはめると間違えやすい。

代表的な例はこのくらいだ。

主体
DBサーバーMySQLのwait_timeoutデフォルト値は8時間
Application Load Balancerconnection idle timeoutのデフォルト値は60秒
Network/Gateway Load BalancerTCP idle timeoutのデフォルトは350秒
NAT Gateway350秒のアイドル後にtimeout
AWS Security Group connection trackingNitroベースインスタンスで設定可能。Nitrov6はデフォルト350秒で、他のインスタンスタイプはデフォルト5日(432000秒)の場合がある

特にALBはconnection idle timeout(デフォルト60秒)とHTTP client keepalive duration(デフォルト1時間)を別々に持つ。


TCP Keepalive
#

TCP keepaliveはOSレベルの機能だ。アイドル状態のTCPソケットに対してカーネルがprobeを送り、接続がまだ生きているか確認する。

重要なポイントは以下の通りだ。

  • TCP keepaliveは自動的に常にオンにはなっていない。
  • ソケットにSO_KEEPALIVEがオンになっている必要がある。
  • probeはアプリケーションデータではなくカーネルレベルのTCP probeだ。

Linuxのデフォルトのsysctl値は通常以下の通りだ。

tcp_keepalive_time   = 7200   # 2時間のアイドル後に開始
tcp_keepalive_intvl  = 75     # 75秒間隔
tcp_keepalive_probes = 9      # 9回失敗でdead判定

このデフォルト値はほとんどのプロキシ/LB/NATのidle timeoutより長い。そのためOSのデフォルト値のままにしていると、TCP keepaliveが動作する前に中間機器が先にidle connectionを整理してしまう可能性がある。

ここで言う中間機器のtimeoutとは、アプリケーションとサーバーの間にあるNAT、ロードバランサー、ファイアウォール、プロキシのような機器が「あまりにも長く静かな接続」を自社のポリシーで整理する時間を意味する。

例えばNATが350秒間何もトラフィックがなければ接続状態を削除するが、LinuxのデフォルトのTCP keepaliveは**7200秒(2時間)**が過ぎてから初めてprobeを送る。するとkeepalive probeを送る前にNATが先に「この接続は長い間アイドルだった」と判断して整理してしまう可能性がある。

もう一つ重要なことがある。

  • TCP keepaliveは多くのNAT/ファイアウォール/NLBの層には助けになりうるが
  • すべての製品がkeepalive probeを「有効なトラフィック」として見てidle timerをリセットするわけではない

つまり「TCP keepaliveをオンにさえすれば、どんなmiddlebox(中間機器)のidle timeoutも必ず切れない」という理解は正しくない。製品によってkeepalive probeの処理方法が異なるからだ。


HikariCPは何を担当するのか?
#

HikariCPはTCPの機能ではなくJDBC connection poolだ。担当する役割は大きく2つだ。

  • すでに開いているDB connectionを再利用する。
  • 死んだconnectionを検出したり、古くなったconnectionを交換したりする。

つまりHikariCPはconnectionを「ずっと持っておくか」「いつ捨てるか」「いつpingするか」を管理する側だ。

核心設定
#

設定意味補足説明
maxLifetimepool内でconnectionが留まれる最大寿命生存確認の機能ではない。 DB/LBのtimeoutより少し短くすることが推奨される。
keepaliveTimeidle connectionに対して定期的にpingを実行maxLifetimeより小さくする必要がある。
connectionTestQueryカスタムのvalidation SQLJDBC4のisValid()をサポートしないlegacyドライバーに主に必要だ。
idleTimeoutidle connectionの削除基準minimumIdle < maximumPoolSizeのときのみ意味がある。
connectionTimeoutpoolからconnectionを待つ時間この時間内にconnectionが取得できなければ失敗する。生存維持とは別の設定だ。

現在のHikariCP READMEの基準ではconnectionTimeout=30sidleTimeout=10mmaxLifetime=30mkeepaliveTime=2mがデフォルト値として文書化されている。ただしkeepaliveTimeは古いバージョンの文書や記事では0と説明されている場合があるため、運用中のライブラリバージョンの文書を合わせて確認する方が安全だ。

HikariCPが実際にpingする方法
#

HikariCPはidle connectionをpingするとき以下のどちらかを使う。

  • JDBC4のConnection.isValid()
  • connectionTestQuery

つまり「HikariCPはvalidation queryなしでまったく検査しない」と言うと不正確だ。より正確な説明はこうだ。

  • 最新のJDBCドライバーならば通常isValid()を使う。
  • legacyドライバーならconnectionTestQueryを使える。
  • keepaliveTimeやpoolから取り出す際の検証で必要なときにpingが発生する。

maxLifetime=30分だから30分間維持されるのか?
#

違う。

maxLifetimeは「30分間生かし続けることを保証する」値ではなく、pool内でconnectionをどれだけの間保管するかを決める値だ。つまりHikariCPの保管ポリシーであり、ネットワークやDBが実際のconnectionを30分間保存してくれるという保証ではない。

例えばDB connectionの経路が以下のようになっているとしよう。

App -> NAT -> DB

そしてポリシーが以下の通りだとする。

  • NAT:350秒のアイドルなら状態を削除
  • DBサーバー:10分のアイドルならconnectionを終了
  • HikariCP:maxLifetime=30分

この場合、実際のconnectionの寿命は30分ではなく、最初に切断する側によって決まる。

  1. アプリケーションがDB connectionを一つ作ってpoolに入れる。
  2. しばらくクエリがまったくない。
  3. 350秒ほど経つとNATが先に状態を削除する可能性がある。
  4. 10分ほど経つとDBサーバーもidle connectionを閉じる可能性がある。
  5. HikariCPは30分まで保管しようとしていたが、実際のconnectionはその前にすでに死んでいる可能性がある。

逆にHTTP connectionの経路ではALBの60秒のidle timeoutのような別のポリシーが適用される。

そのため運用では通常以下のように合わせる。

  1. そのconnectionが実際に通るDB/LB/NAT/ファイアウォールのtimeoutを確認する。
  2. maxLifetimeをそれより少し短くする。
  3. 必要であればkeepaliveTimeやドライバー/OSのTCP keepaliveを一緒に設定する。

またHikariCPの公式READMEはpoolの安定性のためにTCP keepaliveも別途設定するよう推奨している。つまりHikariCPの設定だけでOSソケットのkeepaliveが自動的に設定されるわけではない。


HTTP Keep-Alive vs TCP Keepalive
#

名前が似ているだけで、実際には全く異なるメカニズムだ。

HTTP keep-aliveTCP keepalive
レイヤーL7L4 / OS
目的同じTCP接続で複数のHTTPリクエスト/レスポンスを再利用するアイドルのTCP接続がまだ生きているか確認する
デフォルトの動作HTTP/1.1では持続接続がデフォルトでConnection: closeで終了ソケットにSO_KEEPALIVEがオンの場合のみ動作
誰が担当するかブラウザ、Nginx、WAS、プロキシカーネル / ソケットオプション

まとめると:

  • HTTP keep-aliveは「レスポンス後に接続を閉じないこと」
  • TCP keepaliveは「長くアイドルな接続にprobeを送ること」

だ。


Nginx Keepaliveの構造
#

Client ──[A]──> Nginx ──[B]──> WAS

ここでAとBは互いに異なるkeepaliveポリシーを持つ。

1) Downstream: Client ↔ Nginx
#

この区間は通常HTTP persistent connectionを指す。

keepalive_timeout 75;
keepalive_requests 1000;
  • keepalive_timeoutクライアントとのidle HTTP接続をどれだけ開いておくか
  • keepalive_requests1つの接続でどれだけのリクエストを処理するか

を決める。

2) Upstream: Nginx ↔ WAS
#

この区間はNginxがバックエンドへの接続をキャッシュして再利用する構造だ。

ここで言うupstream connection reuseを非常にシンプルに言うと:

  • リクエスト1つを処理するたびにNginx -> WAS TCP connectionを毎回新しく作ってすぐ切る代わりに
  • リクエスト処理が終わった後でもそのconnectionをしばらくidle状態で残しておき
  • 次のリクエストが同じupstreamサーバーに行く必要があるときそのconnectionを再度使う

ということだ。

つまり核心は**「生きているTCP connectionを次のHTTPリクエストでも再利用する」ということだ。
これは
パフォーマンスの最適化と接続の再利用ポリシー**に近く、後で出てくるTCP keepaliveとは目的が異なる。

upstream backend {
    server 127.0.0.1:8080;
    keepalive 32;
}

location / {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
}

ここで注意すること:

  • keepalive 3232という数は「現在処理中のリクエスト数」を数えるわけでも、「NginxからWASへの全接続数」を数えるわけでもない。
  • この数は各workerがリクエスト処理を終えた後に切らずにidle状態で残すupstream connectionの最大数を意味する。
  • つまり新しいリクエストが来たときに再利用できるよう再利用用に保管しておくconnectionをworker1つあたり最大32個まで持つという意味だ。
  • 例えばworkerが4つなら、idle upstream connectionはworkerごとに最大32個ずつ持てるため、全体のidle connection数は32より多くなる可能性がある。
  • 古い説明のようにkeepalive 32 = バックエンド接続合計32個と理解すると間違いになる。

例えばworker 1が127.0.0.1:8080に開いたconnectionを20個idle状態で持っているとしよう。その状態で新しいリクエストが来て同じupstreamサーバーへ送る必要があれば、Nginxはすでに開いていたidle connectionの一つを取り出して再利用できる。逆にidle connectionが一つもなければそのときに新しいconnectionを作る。

つまりkeepalive 32は**「workerが一つが次のリクエストで再利用しようと切らずに持っているidle upstream connectionを最大32個まで保管する」**という意味で読めばよい。

もう一つはバージョンの違いだ。

  • 長い間NginxはupstreamのHTTPプロキシのデフォルトがHTTP/1.0だったため、proxy_http_version 1.1proxy_set_header Connection ""が事実上必須だった。
  • NGINX 1.29.7からはHTTP upstream keep-aliveがデフォルトの動作に変わった。

それでも設定ファイルを読む人の観点からは、上記の2行を明示的に維持する方がまだ理解しやすい。

3) NginxのTCP keepaliveはさらに別
#

NginxのUpstream connection reuseとOSのTCP keepaliveは別の話だ。

二つの違いを先に分けて見ると理解しやすい。

  • upstream connection reuse:Nginxがリクエスト処理が終わった後でもNginx -> WAS connectionを閉じずに残しておき、次のリクエストに再利用すること
  • TCP keepalive:とても長くidle状態のTCPソケットがまだ生きているかカーネルがprobeで確認すること

つまり前者は**「次のリクエストに再利用するか?」についてのポリシーであり、後者は「このソケットはまだ生きているか?」**を確認するOS機能だ。

必要であれば別途:

proxy_socket_keepalive on;

のようにソケットのSO_KEEPALIVEをオンにできる。

ここでSO_KEEPALIVEこのTCPソケットに対してOSのTCP keepalive機能をオンにするソケットオプションだ。

もう少し詳しく言うと:

  • NginxがバックエンドのWASとTCP connectionを1つ開く。
  • そのconnectionはOSの観点では1つのsocketだ。
  • proxy_socket_keepalive on;を設定するとNginxはそのソケットにSO_KEEPALIVEオプションをオンにする。
  • そするとその後のkeepalive probeの送信有無とタイミングはカーネルのTCP keepalive設定に従う。

重要なのはproxy_socket_keepalive on;だからといってNginxがアプリケーションデータを定期的に送るわけではないということだ。この設定は**OSに「このソケットにはTCP keepaliveを適用してください」**と伝えるものだ。

つまり:

  • keepalive 32workerが一つがリクエストが終わった後でも切らずに残すupstream connectionを何個まで保管するか
  • proxy_socket_keepalive onそのTCPソケットにOSのkeepalive機能をオンにするか

を決める。

つまり、

  • keepalivekeepalive_timeoutHTTP接続の再利用ポリシー
  • proxy_socket_keepaliveTCPソケットのkeepalive

だ。


「接続を維持する技術」の実際の意味
#

特別な魔法があるわけではなく、各レイヤーで接続を閉じずに再利用しようとするポリシーを設けることだ。

1. リクエスト/クエリの処理完了
2. レスポンス後にすぐFINを送らない
3. 一定時間idle状態でconnectionを保管
4. 次のリクエストが来たら同じconnectionを再利用
5. timeoutまたはライフサイクルポリシーが過ぎたら整理

ただし実際の生存は以下がすべて合っている必要がある。

  • アプリケーション/poolのライフサイクルポリシー
  • DBサーバーのidle timeout
  • プロキシ/LB/NAT/ファイアウォールのtimeout
  • 必要に応じてTCP keepaliveまたはアプリケーションレベルのping

Keepaliveのデメリット
#

デメリット説明
メモリ消費idle connectionもsocket bufferと各種per-connection stateを占有する
ファイルディスクリプターの枯渇アイドル接続が増えると新しい接続を受け入れるのが難しくなる可能性がある
古い接続の保持相手が異常終了していても次の使用時点まで分からない場合がある
バックエンド接続の偏りupstream keepaliveが特定のインスタンスにconnectionを長く保持する可能性がある
設定の衝突maxLifetimewait_timeout、LBのidle timeoutが互いに合わないと断続的なエラーが残る

例えば:

同時idle接続100,000個 x 20KB = 約2GB

のようにidle connection自体がリソースコストになりうる。この数値はあくまでも大まかな例であり、実際のメモリ使用量はカーネル/アプリケーション/プロトコルの実装によって異なる。


全区間のKeepalive全体像
#

Client ─── Nginx ─── WAS ─── HikariCP ─── MySQL
  │          │                   │           │
  │          │                   │           └─ DBのidle timeout(例:wait_timeout)
  │          │                   └─ poolの再利用 / keepalive / maxLifetime
  │          └─ downstream/upstream HTTP keep-alive
  └─ クライアント側の持続接続

追加で各TCPソケットには必要に応じてOSのTCP keepaliveをオンにできる

まとめると:

  • HTTP keep-aliveはリクエスト間の再利用
  • HikariCPはDB connectionの再利用と交換ポリシー
  • TCP keepaliveはidle socketの生存確認

を担当する。

この3つは連携できるが、互いに同じ機能ではない。


参考資料
#