負荷試験をかけたら突然 Connection is not available, request timed out が大量に出てきた、なんて経験はないでしょうか。原因の多くは HikariCP の設定をデフォルトのまま本番に出してしまったこと にあります。
Spring Boot はデフォルトで HikariCP を使いますが、そのデフォルト値は「動くこと」を優先した保守的な設定です。本番のトラフィックには必ずチューニングが必要になります。この記事では、主要パラメータの意味と適切な値の導き方を順に見ていきましょう。
なお、HikariCP の前提となる Spring Boot 全体のパフォーマンス改善観点については Spring Data JPAのパフォーマンス最適化 も参考になります。Kubernetes 上で複数 Pod で動かす場合の接続数設計は Spring BootアプリをKubernetesにデプロイする方法 と合わせて検討してください。
なぜ HikariCP か
Spring Boot 2.0 以前はデフォルトのコネクションプールが Tomcat JDBC でした。2.0 から HikariCP に変更されたのは、ベンチマーク上の圧倒的な速さと、コードの軽量さが主な理由です。内部的にロックを最小化した設計になっていて、高スループットな環境でも低レイテンシを維持できます。
ただし「速い」からといって「設定不要」ではありません。むしろ デフォルトは本番向けではない という前提で臨むのが正解です。
コネクションプールが枯渇する仕組み
DB 接続の生成は意外とコストがかかります。TCP 接続を張り、認証を通し、セッションを初期化する一連の処理は、SQL 実行そのものより遅いこともあります。コネクションプールはその接続をあらかじめ作っておき、リクエストが来るたびに貸し出す仕組みです。
問題は、貸し出した接続がなかなか返ってこない状況、つまり スロークエリや接続リーク があると、プールが空になってしまうことです。新たなリクエストは connectionTimeout の時間だけ待たされ、それを過ぎるとタイムアウト例外になります。これが「DB接続の枯渇」です。
主要パラメータを押さえる
maximumPoolSize のデフォルト値と推奨値
デフォルト値: 10。Spring Boot から特に設定しない場合はこの値が適用されます。推奨値: (コア数 × 2) + 1 を出発点に、Tomcat の max-threads や DB 側の max_connections を踏まえて 10〜30 程度。プロパティ名は spring.datasource.hikari.maximum-pool-size です。
minimumIdle のデフォルト値と推奨値
デフォルト値: maximumPoolSize と同値。推奨値: maximumPoolSize と同じ値で固定プールとして運用。HikariCP 公式が明示的に「動的に変動させないこと」を推奨しています。プロパティ名は spring.datasource.hikari.minimum-idle。
connectionTimeout のデフォルト値と推奨値
デフォルト値: 30000ms (30 秒)。推奨値: 3000〜5000ms。30 秒は本番ではほぼ長すぎで、スレッドを長時間ブロックする原因になります。プロパティ名は spring.datasource.hikari.connection-timeout。
idleTimeout のデフォルト値と推奨値
デフォルト値: 600000ms (10 分)。推奨値: デフォルトのままで問題ないことが多い。minimumIdle == maximumPoolSize の場合は無視されます。プロパティ名は spring.datasource.hikari.idle-timeout。
maxLifetime のデフォルト値と推奨値
デフォルト値: 1800000ms (30 分)。推奨値: DB の wait_timeout より数十秒短く。MySQL の wait_timeout デフォルト 8 時間に対し、30 分は十分短いので基本そのまま。プロパティ名は spring.datasource.hikari.max-lifetime。
keepaliveTime のデフォルト値と推奨値
デフォルト値: 0 (無効)。デフォルトでは死活確認は行われません。推奨値: ファイアウォール経由で TCP セッションが切られる環境では 30000〜60000ms (30〜60 秒) を設定。maxLifetime より短く、かつ 30 秒以上である必要があります。プロパティ名は spring.datasource.hikari.keepalive-time。
設定できるパラメータはたくさんありますが、まず以下の6つを理解しておけば大半のケースに対応できます。
| パラメータ | デフォルト値 | 役割 |
|---|---|---|
maximumPoolSize | 10 | プールが保持する最大接続数 |
minimumIdle | maximumPoolSize と同値 | アイドル時に維持する最小接続数 |
connectionTimeout | 30000ms | 接続取得の最大待機時間 |
idleTimeout | 600000ms | アイドル接続を破棄するまでの時間 |
maxLifetime | 1800000ms | 接続の最大生存時間 |
keepaliveTime | 0(無効) | 接続の死活確認間隔 |
minimumIdle は HikariCP 公式が「maximumPoolSize と同じ値にして固定プールとして使うことを推奨する」と明示しています。アイドル時の接続数を変動させると管理が複雑になるためです。
maximumPoolSize の正しい算出方法
「とりあえず大きくすれば安心」と思いがちですが、これは逆効果になることがあります。HikariCP の公式 wiki「About Pool Sizing」には次の式が掲載されています。
connections = (core_count * 2) + effective_spindle_count
effective_spindle_count は回転ディスクの数で、SSD や RDS の場合は 1 と見なすのが一般的です。たとえば 4 コアのアプリサーバーであれば 4 * 2 + 1 = 9、つまり 10 前後が出発点 です。
ただし、これはあくまで目安です。実際にはアプリの同時実行スレッド数を基準にします。Tomcat のデフォルト最大スレッド数は 200 ですが、すべてのスレッドが同時に DB アクセスするわけではありません。実測してプールの使用率(hikaricp.connections.active)を見ながら調整するのが現実的です。
もうひとつ忘れてはいけないのが DB 側の max_connections です。アプリサーバーを複数台動かしている場合、インスタンス数 × maximumPoolSize が DB の上限を超えると DB 側で接続拒否されます。全体の接続数を俯瞰して設計しましょう。
connectionTimeout と idleTimeout の設定ミスで起きる障害
connectionTimeout が短すぎる場合 は、高負荷時に正常なリクエストまで即タイムアウトしてしまいます。逆に 長すぎる場合 は、スレッドが長時間ブロックされてサーバー全体が詰まります。デフォルトの 30 秒はほとんどのケースで長すぎるので、3〜5 秒程度に絞るのがおすすめです。
idleTimeout と maxLifetime については、DB 側の接続タイムアウト設定(MySQL なら wait_timeout、デフォルト 8 時間)との関係に注意が必要です。maxLifetime を DB の wait_timeout より長くすると、DB 側がすでに切断した接続を HikariCP がまだ保持してしまい、次にその接続を使ったときに SQLException が発生します。
目安として、maxLifetime は DB の wait_timeout より 数十秒短く 設定します。RDS の場合は wait_timeout が 8 時間なので、デフォルトの 30 分でも問題ないことが多いですが、ファイアウォールで TCP タイムアウトが短い環境では keepaliveTime も併用しましょう。
application.yml への設定例
最小限の設定はこれだけです。まず maximumPoolSize と connectionTimeout だけでも見直しましょう。
# 最小構成
spring:
datasource:
hikari:
maximum-pool-size: 20
connection-timeout: 3000
本番環境向けの推奨構成はこちらです。環境変数で上書きできるようにしておくと便利です。
# 推奨構成
spring:
datasource:
url: ${DB_URL}
username: ${DB_USER}
password: ${DB_PASSWORD}
hikari:
# コア数・スレッド数・DB max_connectionsを元に算出
maximum-pool-size: ${HIKARI_MAX_POOL_SIZE:20}
minimum-idle: ${HIKARI_MAX_POOL_SIZE:20}
# 高負荷時に正常リクエストを巻き込まない上限
connection-timeout: 3000
# DBのwait_timeout(例:28800s)より短く設定
max-lifetime: 1800000
# アイドル接続は10分で破棄
idle-timeout: 600000
# ファイアウォール環境では切断検知のため有効化
keepalive-time: 60000
pool-name: MyAppPool
設定キーは Spring Boot 2系・3系ともに spring.datasource.hikari.* で変わりません。
設定前後の比較
同時接続 100、うち 20% がスロークエリ(平均 2 秒)という負荷シナリオで比較した結果のイメージです。
| 設定 | maximumPoolSize | connectionTimeout | タイムアウトエラー率 |
|---|---|---|---|
| デフォルト | 10 | 30000ms | 約 40% |
| 調整後 | 20 | 3000ms | 約 3% |
| 過大設定 | 100 | 3000ms | 約 8%(スループット低下) |
過大にすると DB 側のスレッド競合やコンテキストスイッチが増え、逆にスループットが落ちます。「大きければ安全」ではない、というのが HikariCP 公式の主張でもあります。
Actuator でプール使用状況を監視する
よくあるエラーとトラブルシューティング
HikariCP まわりで発生する典型的なエラーは、原因のパターンが限られています。
Connection is not available, request timed out after Nms
connectionTimeout の時間内にプールから接続を取得できなかったときに発生します。原因は主に 3 つです。
- プールサイズが不足している:
hikaricp.connections.activeが常にmaximumPoolSizeに張り付いていれば、サイズ不足です。 - スロークエリで接続が長時間占有されている:
hikaricp.connections.usage(借用時間) のメトリクスを確認し、SQL ログのスロークエリと突き合わせます。 - 接続リーク: アプリケーション側で接続を返却していない可能性。後述の
leakDetectionThresholdで検出できます。
接続リークを検出する: leakDetectionThreshold
leakDetectionThreshold を設定すると、貸し出された接続が指定時間内に返却されない場合に警告ログを出してくれます。本番では負荷の影響があるため、ステージング環境での利用をおすすめします。
spring:
datasource:
hikari:
# 60秒以上返却されない接続を警告
leak-detection-threshold: 60000
警告ログにはリークしているコードのスタックトレースが出力されるので、原因コードを特定しやすくなります。
HikariPool-1 - Failed to validate connection
maxLifetime が DB の wait_timeout より長い場合に頻発します。DB 側がすでに切断した接続を HikariCP が再利用しようとして失敗するパターンです。maxLifetime を DB の wait_timeout より短く設定し直してください。
チューニング後は、プールが実際にどう動いているかを確認しましょう。spring-boot-actuator と micrometer-core を依存に追加すると、HikariCP のメトリクスが公開されます。
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# メトリクスエンドポイントを有効化
management:
endpoints:
web:
exposure:
include: metrics
/actuator/metrics/hikaricp.connections.active で現在使用中の接続数、hikaricp.connections.pending で待機中のスレッド数が確認できます。待機が常時発生しているようなら maximumPoolSize を増やすサインです。Prometheus と Grafana を使った詳細な可視化は Spring BootのObservability設定(Micrometer+Prometheus+Grafana) を参考にしてください。
設定見直しチェックリスト
自プロジェクトの設定を確認する際は、以下を一通り見直してみてください。
maximumPoolSizeをデフォルト(10)のまま本番に出していないかmaxLifetimeが DB のwait_timeoutより長くなっていないかconnectionTimeoutが 30000ms(デフォルト)のままになっていないか- Tomcat の
max-threadsとmaximumPoolSizeのバランスは取れているか - 複数インスタンス構成で
インスタンス数 × poolSizeが DB のmax_connectionsを超えていないか
まとめ
HikariCP のデフォルト設定は開発用途には十分ですが、本番トラフィックには必ず見直しが必要です。まず maximumPoolSize と connectionTimeout を実態に合わせて調整し、Actuator でメトリクスを見ながら少しずつ詰めていくのが安全な進め方です。
JPA のクエリ最適化については Spring Data JPAのパフォーマンス最適化、Virtual Threads との組み合わせについては Java 21仮想スレッドとSpring Boot も合わせて読んでみてください。コネクションプールの設定が落ち着いたら、次はトランザクション管理も整理しておくと障害の切り分けが楽になりますよ。