このドキュメントは疑問に思っていた点をAIエージェントとの対話を通じて整理したメモです。TCP接続がすぐに切れない理由と、TCP keepalive、HTTP keep-alive、HikariCP keepaliveの違いを整理します。
まず区別すべきこと#
このドキュメントで「keepalive」という言葉は実際には3つの異なるものを指す。
| 名称 | レイヤー | 実際の意味 |
|---|---|---|
| TCP keepalive | L4 / OS | アイドル状態のTCPソケットに対してカーネルが生存確認probeを送る |
| HTTP keep-alive | L7 / 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 Balancer | connection idle timeoutのデフォルト値は60秒 |
| Network/Gateway Load Balancer | TCP idle timeoutのデフォルトは350秒 |
| NAT Gateway | 350秒のアイドル後にtimeout |
| AWS Security Group connection tracking | Nitroベースインスタンスで設定可能。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するか」を管理する側だ。
核心設定#
| 設定 | 意味 | 補足説明 |
|---|---|---|
maxLifetime | pool内でconnectionが留まれる最大寿命 | 生存確認の機能ではない。 DB/LBのtimeoutより少し短くすることが推奨される。 |
keepaliveTime | idle connectionに対して定期的にpingを実行 | maxLifetimeより小さくする必要がある。 |
connectionTestQuery | カスタムのvalidation SQL | JDBC4のisValid()をサポートしないlegacyドライバーに主に必要だ。 |
idleTimeout | idle connectionの削除基準 | minimumIdle < maximumPoolSizeのときのみ意味がある。 |
connectionTimeout | poolからconnectionを待つ時間 | この時間内にconnectionが取得できなければ失敗する。生存維持とは別の設定だ。 |
現在のHikariCP READMEの基準ではconnectionTimeout=30s、idleTimeout=10m、maxLifetime=30m、keepaliveTime=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分ではなく、最初に切断する側によって決まる。
- アプリケーションがDB connectionを一つ作ってpoolに入れる。
- しばらくクエリがまったくない。
- 350秒ほど経つとNATが先に状態を削除する可能性がある。
- 10分ほど経つとDBサーバーもidle connectionを閉じる可能性がある。
- HikariCPは30分まで保管しようとしていたが、実際のconnectionはその前にすでに死んでいる可能性がある。
逆にHTTP connectionの経路ではALBの60秒のidle timeoutのような別のポリシーが適用される。
そのため運用では通常以下のように合わせる。
- そのconnectionが実際に通るDB/LB/NAT/ファイアウォールのtimeoutを確認する。
maxLifetimeをそれより少し短くする。- 必要であれば
keepaliveTimeやドライバー/OSのTCP keepaliveを一緒に設定する。
またHikariCPの公式READMEはpoolの安定性のためにTCP keepaliveも別途設定するよう推奨している。つまりHikariCPの設定だけでOSソケットのkeepaliveが自動的に設定されるわけではない。
HTTP Keep-Alive vs TCP Keepalive#
名前が似ているだけで、実際には全く異なるメカニズムだ。
| HTTP keep-alive | TCP keepalive | |
|---|---|---|
| レイヤー | L7 | L4 / 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_requestsは1つの接続でどれだけのリクエストを処理するか
を決める。
2) Upstream: Nginx ↔ WAS#
この区間はNginxがバックエンドへの接続をキャッシュして再利用する構造だ。
ここで言うupstream connection reuseを非常にシンプルに言うと:
- リクエスト1つを処理するたびに
Nginx -> WASTCP 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 32の32という数は「現在処理中のリクエスト数」を数えるわけでも、「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.1とproxy_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 -> WASconnectionを閉じずに残しておき、次のリクエストに再利用すること - 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 32はworkerが一つがリクエストが終わった後でも切らずに残すupstream connectionを何個まで保管するかproxy_socket_keepalive onはそのTCPソケットにOSのkeepalive機能をオンにするか
を決める。
つまり、
keepalive、keepalive_timeoutはHTTP接続の再利用ポリシーproxy_socket_keepaliveはTCPソケットの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を長く保持する可能性がある |
| 設定の衝突 | maxLifetime、wait_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つは連携できるが、互いに同じ機能ではない。
参考資料#
- Linux Kernel Documentation - IP Sysctl (
tcp_keepalive_time,tcp_keepalive_intvl,tcp_keepalive_probes) - HikariCP README - Configuration
- NGINX Official Docs -
keepalive_timeout,keepalive_requests - NGINX Official Docs -
ngx_http_upstream_modulekeepalive - NGINX Official Docs -
proxy_socket_keepalive - NGINX Official Docs - CHANGES (
1.29.7) - MySQL 8.4 Reference Manual -
wait_timeout - AWS Docs - Application Load Balancer connection idle timeout
- AWS Docs - Network Load Balancer TCP idle timeout
- AWS Docs - Troubleshoot NAT gateways(350秒 idle timeout)
- AWS Docs - EC2 security group connection tracking
- AWS Docs - Classic Load Balancer idle timeout (
TCP keep-alive probes do not prevent termination)
