ABOUT ME

와주셔서 감사합니다. 좋은 글을 많이 쓰겠습니다.

Today
Yesterday
Total
  • [HikariCP] connectionTimeout 설정 알아보기
    Java 2024. 7. 11. 05:56
    반응형

    - 목차

     

    connectionTimeout 이란 ?

    HikariPool 은 Java 에서 사용하는 Connection Pool 모듈입니다.

    지정된 갯수만큼 많은 양의 Connection 을 미리 생성해두며, 생성된 Connection 을 하나씩 사용할 수 있습니다.

     

     

    아래의 이미지는 2개의 Connection 이 존재하고, 3번의 Connection 요청이 발생하는 상황입니다.

    Connection Pool 에서 Connection 을 요청한 2개의 Client 또는 Thread 는 정상적으로 Connection 을 사용할 수 있습니다.

    반면 3번째 Client 또는 Thread 는 Connection Pool 에 더 이상 Connection 이 없기 때문에 Connection 을 획득할 수 없습니다.

    이 상황에서 특정 시간동안 3번째 Client 또는 Thread  가 Connection 을 획득하지 못한다면 SQLTransientConnectionException 이 발생합니다.

    이러한 예외 상황의 기준이 되는 Connection 획득 시간제한이 바로 Connection Timeout 이됩니다.

     

     

     

    ConnectionTimeout 상황.

     

    아래의 코드는 2개의 Connection 을 가지는 Connection Pool 과 3개의 Connection 요청을 구현한 코드입니다.

    Connection 의 요청와 Connection Pool 용량이 서로 비대칭인 상태를 만들고, Connection Timeout 의 상황을 재현합니다.

     

    import com.zaxxer.hikari.HikariConfig;
    import com.zaxxer.hikari.HikariDataSource;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.SQLTransientConnectionException;
    
    public class HikariCPExample {
        public static void main(String[] args) {
            HikariConfig config = new HikariConfig();
            config.setJdbcUrl("jdbc:mysql://mysql:3306/mysql");
            config.setUsername("root");
            config.setPassword("1234");
    
            config.setConnectionTimeout(5000); // 연결 타임아웃 5초
            config.setMaximumPoolSize(2); // 최대 커넥션 개수 2개
    
            HikariDataSource dataSource = new HikariDataSource(config);
    
            try {
                long start, end, duration;
    
                // 첫 번째 Connection
                start = System.nanoTime();
                Connection conn1 = dataSource.getConnection();
                end = System.nanoTime();
                duration = (end - start) / 1_000_000; // 나노초 -> 밀리초 변환
                System.out.println("✅ Connection 1 획득 시간: " + duration + "ms");
    
                // 두 번째 Connection
                start = System.nanoTime();
                Connection conn2 = dataSource.getConnection();
                end = System.nanoTime();
                duration = (end - start) / 1_000_000;
                System.out.println("✅ Connection 2 획득 시간: " + duration + "ms");
    
                // 세 번째 Connection (최대 풀 크기 초과)
                start = System.nanoTime();
                try {
                    Connection conn3 = dataSource.getConnection();
                    end = System.nanoTime();
                    duration = (end - start) / 1_000_000;
                    System.out.println("✅ Connection 3 획득 시간: " + duration + "ms");
                } catch (SQLTransientConnectionException e) {
                    end = System.nanoTime();
                    duration = (end - start) / 1_000_000;
                    System.out.println("⚠️ Connection 3 획득 실패 (소요 시간: " + duration + "ms) - " + e.getMessage());
                }
    
                // 연결 닫기
                conn1.close();
                conn2.close();
            } catch (SQLException e) {
                System.out.println("❌ SQL 예외 발생: " + e.getMessage());
                e.printStackTrace();
            } finally {
                dataSource.close();
            }
        }
    }

     

    Connection Timeout 을 5초로 설정하였기 때문에 세번째 Connection 을 요청하는 코드는 5초 간의 Blocking Time 이 발생하게 됩니다. 

    [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
    [main] INFO com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@45c7e403
    [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
    ✅ Connection 1 획득 시간: 0ms
    ✅ Connection 2 획득 시간: 14ms
    ⚠️ Connection 3 획득 실패 (소요 시간: 5026ms) - HikariPool-1 - Connection is not available, request timed out after 5008ms (total=2, active=2, idle=0, waiting=0)
    [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
    [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.

     

     

    MySQL 의 max_connections 갯수와 Connection Pool 의 관계.

    데이터베이스가 허용하는 Connection 의 최대치보다 Connection Pool 의 사이즈가 더 큰 경우에 Connection Timeout 의 상황을 똑같이 발생합니다.

    MySQL 의 경우에 max_connections 라는 설정으로 MySQL 이 허용하는 Connection 의 최대치가 존재합니다.

     

    의도적으로 max_connections 의 값을 3개로 고정하고, HikariPool DataSource 의 풀 사이즈를 10개로 설정합니다.

    show variables like 'max_connections';
    +-----------------+-------+
    | Variable_name   | Value |
    +-----------------+-------+
    | max_connections | 3     |
    +-----------------+-------+
    1 row in set (0.00 sec)
    import com.zaxxer.hikari.HikariConfig;
    import com.zaxxer.hikari.HikariDataSource;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.SQLTransientConnectionException;
    
    public class HikariCPExample {
        public static void main(String[] args) {
            HikariConfig config = new HikariConfig();
            config.setJdbcUrl("jdbc:mysql://mysql:3306/mysql");
            config.setUsername("root");
            config.setPassword("1234");
    
            config.setConnectionTimeout(5000); // 연결 타임아웃 5초
            config.setMaximumPoolSize(10); // 최대 커넥션 개수 10개
    
            HikariDataSource dataSource = new HikariDataSource(config);
    
            try {
                Connection conn1 = dataSource.getConnection();
                Connection conn2 = dataSource.getConnection();
                Connection conn3 = dataSource.getConnection();
                Connection conn4 = dataSource.getConnection();
                Connection conn5 = dataSource.getConnection();
                Connection conn6 = dataSource.getConnection();
            } catch (SQLException e) {
                System.out.println("❌ SQL 예외 발생: " + e.getMessage());
                e.printStackTrace();
            } finally {
                dataSource.close();
            }
        }
    }

     

    아래의 출력 내용을 확인해보면 max_connections 의 사이즈가 3이기 때문에 HikariPool DataSource 의 Total Connection 의 갯수도 3으로 설정됩니다.

    MaximumPoolSize 가 10으로 설정되었어도 데이터베이스의 설정을 따릅니다.

    그리고 Connection Timeout 시간이 흐른 후에 Connection 획득에 실패하고 SQLTransientConnectionException 을 Raise 합니다.

    [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
    [main] INFO com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@45c7e403
    [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
    ❌ SQL 예외 발생: HikariPool-1 - Connection is not available, request timed out after 5006ms (total=3, active=3, idle=0, waiting=0)
    java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 5006ms (total=3, active=3, idle=0, waiting=0)
    	at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:686)
    	at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:179)
    	at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:144)
    	at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:99)
    	at HikariCPExample.main(HikariCPExample.java:23)
    Caused by: java.sql.SQLNonTransientConnectionException: Data source rejected establishment of connection,  message from server: "Too many connections"
    	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:111)
    	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
    	at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:825)
    	at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:446)
    	at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:239)
    	at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:188)
    	at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:137)
    	at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:360)
    	at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:202)
    	at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:461)
    	at com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator.call(HikariPool.java:724)
    	at com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator.call(HikariPool.java:703)
    	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    	at java.base/java.lang.Thread.run(Thread.java:829)
    [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
    [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.

     

     

     

    관련 코드.

    docker network create mysql 
    docker run -d --platform linux/amd64 --network mysql --name mysql --hostname mysql -e MYSQL_ROOT_PASSWORD=1234 mysql:8.0.23
    docker run -it --network mysql --rm -v /tmp/HikariCPExample.java:/tmp/HikariCPExample.java openjdk:11 /bin/bash
    
    cd /tmp/
    wget https://repo1.maven.org/maven2/com/zaxxer/HikariCP/5.1.0/HikariCP-5.1.0.jar
    wget https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar
    wget https://repo1.maven.org/maven2/org/slf4j/slf4j-simple/1.7.36/slf4j-simple-1.7.36.jar
    wget https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.0.33/mysql-connector-j-8.0.33.jar -P /tmp
    
    javac -cp HikariCP-5.1.0.jar:slf4j-api-1.7.36.jar:slf4j-simple-1.7.36.jar:mysql-connector-j-8.0.33.jar HikariCPExample.java
    java -cp .:HikariCP-5.1.0.jar:slf4j-api-1.7.36.jar:slf4j-simple-1.7.36.jar:mysql-connector-j-8.0.33.jar HikariCPExample

     

    반응형
Designed by Tistory.