負荷試験をかけたら突然 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つを理解しておけば大半のケースに対応できます。

パラメータデフォルト値役割
maximumPoolSize10プールが保持する最大接続数
minimumIdlemaximumPoolSize と同値アイドル時に維持する最小接続数
connectionTimeout30000ms接続取得の最大待機時間
idleTimeout600000msアイドル接続を破棄するまでの時間
maxLifetime1800000ms接続の最大生存時間
keepaliveTime0(無効)接続の死活確認間隔

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 への設定例

最小限の設定はこれだけです。まず maximumPoolSizeconnectionTimeout だけでも見直しましょう。

# 最小構成
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 秒)という負荷シナリオで比較した結果のイメージです。

設定maximumPoolSizeconnectionTimeoutタイムアウトエラー率
デフォルト1030000ms約 40%
調整後203000ms約 3%
過大設定1003000ms約 8%(スループット低下)

過大にすると DB 側のスレッド競合やコンテキストスイッチが増え、逆にスループットが落ちます。「大きければ安全」ではない、というのが HikariCP 公式の主張でもあります。

Actuator でプール使用状況を監視する

よくあるエラーとトラブルシューティング

HikariCP まわりで発生する典型的なエラーは、原因のパターンが限られています。

Connection is not available, request timed out after Nms

connectionTimeout の時間内にプールから接続を取得できなかったときに発生します。原因は主に 3 つです。

  1. プールサイズが不足している: hikaricp.connections.active が常に maximumPoolSize に張り付いていれば、サイズ不足です。
  2. スロークエリで接続が長時間占有されている: hikaricp.connections.usage (借用時間) のメトリクスを確認し、SQL ログのスロークエリと突き合わせます。
  3. 接続リーク: アプリケーション側で接続を返却していない可能性。後述の 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-actuatormicrometer-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-threadsmaximumPoolSize のバランスは取れているか
  • 複数インスタンス構成で インスタンス数 × poolSize が DB の max_connections を超えていないか

まとめ

HikariCP のデフォルト設定は開発用途には十分ですが、本番トラフィックには必ず見直しが必要です。まず maximumPoolSizeconnectionTimeout を実態に合わせて調整し、Actuator でメトリクスを見ながら少しずつ詰めていくのが安全な進め方です。

JPA のクエリ最適化については Spring Data JPAのパフォーマンス最適化、Virtual Threads との組み合わせについては Java 21仮想スレッドとSpring Boot も合わせて読んでみてください。コネクションプールの設定が落ち着いたら、次はトランザクション管理も整理しておくと障害の切り分けが楽になりますよ。