HikariCPの接続タイムアウト問題に対する原因分析と解決策

HikariCPの接続タイムアウト問題に対する原因分析と解決策

Javaアプリケーションでデータベースとの通信を行う際に、HikariCPを使用した接続プールが「接続不可」というエラーを発生させることがあります。この記事では、そのような状況における問題の原因と対処方法について詳しく説明します。

エラーログの概要

[2025-10-12 14:35:12.888] ERROR 12345 --- [nio-8080-exec-5] c.e.u.s.impl.UserServiceImpl : [UserService] Failed to get user by id: 15002
org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.
...
Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.
...
Caused by: java.sql.SQLException: Connections could not be acquired from the underlying database!

ビジネスシーン

ユーザーが`/api/user/15002`エンドポイントにアクセスする際に、以下のような条件で例外が発生しました:

  • 環境情報:JDK 1.8.0_361(64-bit)、Windows 10 10.0.19042、Spring Boot 2.7.5、HikariCP 4.0.3
  • 操作手順:認証後にHTTP GETリクエストを送信
  • 問題の現象:401未承認エラーが返され、DB接続プールログに「Connection is not available」の記録
  • 影響範囲:日間平均2000回以上のリクエストにおいて約5%がエラー、ピーク時にはエラー率が15%に達する

問題の調査プロセス

1. 初期分析

以下の点を確認しました:

  • エラーメッセージは、データベース接続プールのパフォーマンス不足やセキュリティ設定の衝突を示唆
  • 主なキーワード:
    • エラークラス:`SQLTransientConnectionException`
    • メッセージ:`Connection is not available, request timed out after 30000ms`
    • 発生箇所:`UserServiceImpl$$EnhancerBySpringCGLIB$$1.getUserById`

2. 詳細な調査ステップ

[ステップ1] データベース接続プール設定の確認

`application.properties`ファイル内の設定を確認しました:

spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.connection-timeout=30000
spring.datasource.url=jdbc:mysql://192.168.1.100:3306/security_db

結果として、最大接続数が20であり、現在のデータベースインスタンスには50の並行接続があるため、接続プールの設定が適切でないことが判明しました。

[ステップ2] セキュリティ認証フローの検証

Postmanを使用して`POST /auth/login`リクエストをシミュレートし、トークンの有効性をテストしました。結果として、サービス側がトークンを正しく検証できていないことがわかりました。

[ステップ3] 公式ドキュメントの参照

Spring Security 5.7.1の公式ドキュメントを参照し、`@PreAuthorize`アノテーションがトランザクション管理と共に使用される必要があることを確認しました。

[ステップ4] 圧力テストの実施

JMeterを使用して500の並列リクエストをシミュレートし、負荷テストを行いました。初期段階ではエラー率が0%でしたが、30分後にはエラー率が40%に上昇しました。

試みた解決策

解決策1:データベース接続プールパラメータの調整

監視データに基づき、接続数が不足していることを考慮して次の変更を加えました:

spring.datasource.hikari.maximum-pool-size=50
spring.datasource.hikari.minimum-idle=30
spring.datasource.hikari.connection-timeout=5000
spring.datasource.hikari.validation-timeout=10000

これによりエラー率は15%から8%に低下しましたが、偶発的なタイムアウトが依然として発生しました。

解決策2:セキュリティ認証フローの最適化

`UserServiceImpl`クラスに`@Transactional(readOnly=true)`を追加し、`@PreAuthorize("hasRole('ADMIN')")`をメソッドレベルに移動しました。これにより401エラーが70%減少しましたが、新しい例外`NoUserfoundException`が発生しました。

解決策3:トランザクション管理モジュールの再設計

Spring SecurityとSpring AOPの衝突に対応するために、トランザクション管理を次のように再設計しました:

@Configuration
@EnableTransactionManagement
public class TransactionConfig {
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

最終的な有効な解決策

以下の組み合わせにより、システムは安定し、関連する異常はその後の2週間にわたって発生しませんでした:

  • データベース接続プールの最適化
  • トランザクション管理の再設計
  • セキュリティ認証の統合
  • 監視アラートの設定

コード例

以下は、セキュリティユーザーデータサービスの実装例です:

@Service
@Transactional(readOnly = true)
public class SecurityUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) {
        final var user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        return new org.springframework.security.core.userdetails.User(
            user.getUsername(),
            user.getPassword(),
            true,
            true,
            true,
            true,
            AuthorityUtils.createAuthorityList(user.getRoles().stream()
                .map(Role::getName)
                .toArray(String[]::new))
        );
    }
}

タグ: SpringBoot hikaricp DatabaseConnectionPooling

8月4日 00:22 投稿