問題の概要
アプリケーションがデータベース接続を求めていますが、プールが枯渇しています。現在、すべての接続が使用中です。デフォルトでは、HikariCPは接続がプールに戻るのを30秒間(30,000ミリ秒)待機します。その時間を過ぎても空きが出ない場合、アプリは SQLTransientConnectionException をスローし、リクエストは失敗します。
java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms
at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:696)
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:197)
接続が枯渇する理由
- コードのリーク: 接続が取得されたままプールに返却されないケースです。レガシーなJDBCコードで
finallyブロックが不足している場合によく発生します。 - ボトルネックとなるクエリ: 1つに5〜10秒かかるクエリが数件あるだけで、小さなプールはすぐに飽和してしまいます。
- プールのプロビジョニング不足: デフォルトの接続数10を使用しているが、トラフィックには25が必要な場合などです。
- トランザクションの肥大化: 低速なサードパーティのREST APIからのレスポンスを待っている間、データベース接続を保持し続けている状態です。
解決策1:コネクションリークの検出と修正
リークは、低トラフィック時でもプールが枯渇し続ける主な原因です。Spring Data JPAはその大部分を自動で処理しますが、カスタムのJDBCロジックではクローズ命令が漏れることがよくあります。HikariCPには、リークがどこで発生しているかを特定する機能が組み込まれています。
以下のプロパティを application.properties に追加してください:
# 接続が2秒以上保持されている場合、スタックトレースをログに出力する
spring.datasource.hikari.leak-detection-threshold=2000
この設定を有効にすると、ログに接続を取得した特定のメソッドが示されます。「Apparent connection leak detected」というメッセージに続くスタックトレースを確認してください。コードを修正すれば、エラーは解消されるはずです。
解決策2:プールサイズの適正化
デフォルトのプールサイズ10はかなり控えめな設定です。中程度の負荷がかかる本番環境のマイクロサービスでは、この値を増やす必要があるかもしれません。ただし、500のような極端に大きな数値に設定したくなる誘惑は避けてください。データベースのCPUがコンテキストスイッチによって過負荷になる可能性があります。
# DBハードウェアに基づいて最大接続数を増やす (例: 20-50)
spring.datasource.hikari.maximum-pool-size=30
# 事前に準備しておく接続数を維持する
spring.datasource.hikari.minimum-idle=10
# クライアントが諦めるまでの待機時間 (通常は30秒で十分)
spring.datasource.hikari.connection-timeout=30000
注意: 常にデータベースサーバーのキャパシティを確認してください。アプリのインスタンスが5つあり、それぞれ maximum-pool-size を30に設定した場合、データベースは少なくとも150の同時接続を処理できる必要があります。
解決策3:トランザクションと外部I/Oの分離
最も頻繁に見られる間違いの1つは、ネットワーク呼び出しを行うサービスメソッド全体を @Transactional でラップすることです。リモートAPIのレスポンスに5秒かかる場合、その5秒間、データベース接続はアイドル状態で無駄に使用されたままになります。
間違った方法:
@Transactional
public void completePurchase(Order order) {
orderRepo.save(order); // ここで接続を取得
// 危険: 決済ゲートウェイの待機中に接続が保持される
paymentService.chargeCreditCard(order.getAmount());
orderRepo.updateStatus(order, "PAID");
} // ここでようやく接続が解放される
正しい方法: トランザクションは最小限に抑えてください。データベースへの書き込みを実行してコミットし、APIを呼び出し、その後にステータスを更新するための2つ目のトランザクションを開始します。これにより、API呼び出しの実行中に接続を他のユーザーに解放できます。
解決策4:プールの状態監視
推測をやめて計測を始めましょう。Spring Boot Actuatorを使用すると、アクティブな接続数や待機中の接続数をリアルタイムで正確に把握できます。pom.xml に以下を追加してください:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
設定でメトリクスを有効にします:
management.endpoints.web.exposure.include=metrics
management.endpoint.metrics.enabled=true
/actuator/metrics/hikaricp.connections.pending エンドポイントを確認してください。「pending(保留中)」のカウントが常に0より大きい場合は、プールが小さすぎるか、クエリが遅すぎて需要に追いついていないことを示す明確なサインです。

