ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring Boot] This connection has been closed
    이슈 해결 2019. 3. 5. 14:07

    This connection has been closed.


    실제 서버를 운영하고 있는 도중 위와 같은 에러가 발생했다.


    커넥션이 닫혔다는 내용인데, 그 이유야 여러가지가 있을 것이다.


    "HikariPool-1 - Connection is not available, request timed out after 30003ms."


    내가 경험한 운영 환경에서는 위와 같은 에러가 나왔다.


    클라이언트로부터 요청이 서버로 전달되었고, 서버에서 DB 서버로 요청을 연결하여 원하는 결과를 전달해주어야 하는데, 

    여유 커넥션이 존재하지 않아 요청 시간이 30초를 넘어버려 발생하는 것으로 판단된다. 


    실제 위 로그 내용을 보면 30003ms (30.003초) 동안 요청 대기가 발생해서 하이버네이트 예외 중 JDBCConnectionException 예외가 발생한거다.



    이후 모든 요청 연결들에 대해 예외가 발생하여 부랴부랴 해당 내용을 검색하고 해결방안을 찾아보았다.


    스택오버플로우, 티스토리 블로그, 톰캣 아파치 공식 도큐먼트 문서를 찾아보게 되었고, 그 결과 커넥션 풀에 대한 다양한 설정들을 접할 수 있었다.


    다양한 이유가 있겠지만, 해당 문제의 직접적인 원인과 추후 발생할 수 있는 요지에 대하여 설정들을 찾아 적용했다.



    2019-03-05 ---

    위의 문제에서 발생할 수 있는 핵심 요소는 다음과 같다.

     - 커넥션 풀에 의해 최대 동시 할당할 커넥션의 연결 갯수


    그리고 추후 발생할 수 있는 요소에 대해서는 다음과 같은 상황을 생각했다.

     - 유휴 상태(idle)에서의 커넥션 풀이 유효한지 검증


    유휴란 요청이 전달되지 않아 작업할 것이 없이 대기 상태에 머무르는 것을 의미한다.



    2019-03-05 ---

    HikariCP를 쓰면서 실제 yml에 DBCP에 대한 설정을 맞춰버렸다...


    이후에 HikariCP는 설정해주는 속성들이 DBCP와 다르다는 것을 알아 차리고 maxLifetime 속성만 설정해주고 운영하는 중이다.


    참고로 maxLifetime은 데이터베이스에서 사용하는 타임아웃 설정[각주:1]을 기준으로 조절해야하는데, PostgreSQL의 경우 default 값이 0이다...


    그래서 일단 statement_timeout의 값을 30초 정도로 설정해주고 maxLifetime은 25초 정도로 설정했다.



    추후 문제가 발생하면 한번 더 검토해보고 시도해볼 것이 있는지 찾아봐야할 것 같다.


    선임 혹은 팀장이 왜 신입들에게 운영 관련된 내용과 업무를 맡기지 않으려 하는지 알 것 같다.


    신입은 워낙 경험이 없고 얕은 지식 상태를 가지고 있으니 이런 광범위하고 깊은 데이터를 쉽게 이해할 수가 없다.


    이런 정보들을 보고 바로 이해하는 신입은 구글에서 일하면 될 것 같다.







    properties 혹은 yml에서 스프링부트로 운영할 때 DB 커넥션 풀에 대해 설정할 수 있는 옵션들이다.


    HikariCP 옵션


    옵션 

    설명 

     autoCommit

     auto-commit설정 (default: true)

     connectionTimeout

     pool에서 커넥션을 얻어오기전까지 기다리는 최대 시간. 

    허용가능한 wait time을 초과하면 SQLException을 던짐. 

    설정가능한 가장 작은 시간은 250ms (default: 30000 (30s))

     idleTimeout

     pool에 일을 안하는 커넥션을 유지하는 시간. 

    이 옵션은 minimumIdle이 maximumPoolSize보다 작게 설정되어 있을 때만 설정. 

    pool에서 유지하는 최소 커넥션 수는 minimumIdle (A connection will never be retired as idle before this timeout.). 

    최솟값은 10000ms (default: 600000 (10minutes))

     maxLifetime

     커넥션 풀에서 살아있을 수 있는 커넥션의 최대 수명시간. 

    사용중인 커넥션은 maxLifetime에 상관없이 제거되지않음. 사용중이지 않을 때만 제거됨. 

    풀 전체가아닌 커넥션 별로 적용이되는데 그 이유는 풀에서 대량으로 커넥션들이 제거되는 것을 방지하기 위함임. 

    강력하게 설정해야하는 설정 값으로 데이터베이스나 인프라의 적용된 connection time limit보다 작아야함

    0으로 설정하면 infinite lifetime이 적용됨 (idleTimeout설정 값에 따라 적용 idleTimeout값이 설정되어 있을 경우 0으로 설정해도 무한 lifetime 적용 안됨).

    (default: 1800000 (30minutes))

     connectionTestQuery

     JDBC4 드라이버를 지원한다면 이 옵션은 설정하지 않는 것을 추천

    이 옵션은 JDBC4를 지원안하는 드라이버를 위한 옵션임 (Connection.isValid() API.) 

    커넥션 pool에서 커넥션을 획득하기전에 살아있는 커넥션인지 확인하기 위해 valid 쿼리를 던지는데 사용되는 쿼리 (보통 SELECT 1 로 설정) JDBC4드라이버를 지원하지않는 환경에서 이 값을 설정하지 않는다면 error레벨 로그를 뱉어냄.(default: none)

     minimumIdle

     아무런 일을 하지않아도 적어도 이 옵션에 설정 값 size로 커넥션들을 유지해주는 설정. 

    최적의 성능과 응답성을 요구한다면 이 값은 설정하지 않는게 좋음

    default값을 보면 이해할 수있음. (default: same as maximumPoolSize)

     maximumPoolSize

      pool에 유지시킬수 있는 최대 커넥션 수. 

    pool의 커넥션 수가 옵션 값에 도달하게 되면 idle인 상태는 존재하지 않음. (default: 10)

     poolName

     이 옵션은 사용자가 pool의 이름을 지정함. 

    로깅이나 JMX management console에 표시되는 이름.(default: auto-generated)

     initializationFailTimeout

     이 옵션은 pool에서 커넥션을 초기화할 때 성공적으로 수행할 수 없을 경우 빠르게 실패하도록 해준다. [각주:2]

     readOnly

     pool에서 커넥션을 획득할 때 read-only 모드로 가져옴. 

    몇몇의 database는 read-only모드를 지원하지 않음. 

    커넥션이 read-only로 설정되어있으면 몇몇의 쿼리들이 최적화 됨. (default: false)

     driverClassName

     HikariCP는 jdbcUrl을 참조하여 자동으로 driver를 설정하려고 시도함. 

    하지만 몇몇의 오래된 driver들은 driverClassName을 명시화 해야함. 

    어떤 에러 메시지가 명백하게 표시 되지않는다면 생략해도됨.

     validationTimeout

     valid 쿼리를 통해 커넥션이 유효한지 검사할 때 사용되는 timeout. 

    250ms가 설정될 수 있는 최솟값(default: 5000ms)

     leakDetectionThreshold

     커넥션이 누수 로그메시지가 나오기 전에 커넥션을 검사하여 pool에서 커넥션을 내보낼 수 있는 시간. 

    0으로 설정하면 leak detection을 이용하지않음. 최솟값 2000ms (default: 0)



    DBCP 옵션


     옵션

    설명 

     spring.datasource.tomcat.max-active = 10

     풀에서 동시에 할당할 수 있는 커넥션의 최대 연결 갯수 default : 100

     spring.datasource.tomcat.initial-size = 2

     풀이 시작될 때 만들어지는 커넥션의 초기 연결 갯수 default : 10

     spring.datasource.tomcat.max-idle = 2

     idle 상태에서 풀에 항상 유지되어야하는 최대 커넥션의 연결 갯수 default : 100

     spring.datasource.tomcat.min-idle = 1

     idle 상태에서 풀에 항상 유지되어야하는 최소 커넥션의 연결 갯수 default : 10

     spring.datasource.test-on-borrow = true

     풀에서 커넥션을 가져오기 전에 유효성 검사 여부 default : false

    운영 환경에서 필수 설정이다. 

    기본 값은 false이며, true로 설정하면 커넥션 풀에서 커넥션을 빌릴 때 마다 유효한 커넥션인지 검사한다. 

    끊긴 커넥션일 경우 커넥션 풀에서 해당 커넥션을 버리도록 한다. 

    이 옵션은 단독적으로 작동할 수 없으며 validationQuery 설정을 필요로 한다.

     spring.datasource.test-while-idle = true

     idle 상태에서 유효성 검사 여부 default : false

     spring.datasource.time-between-eviction-runs-millis = 60000

     유휴(idle) 연결 검증이나 쓰레드에서 제거될 때 설정 default : 5000(5초)
     * 주의 : 1초 미만으로 설정하면 안된다.

     spring.datasource.tomcat.min-evictable-idle-time-millis = 7200000

     객체가 끊어지기 전 풀에서 유휴(idle) 상태로 있을 수 있는 최소 시간 설정 default : 60000(60초)

     spring.datasource.validation-query = SELECT 1

     유효 커넥션 여부를 실제 검증하는 쿼리문을 작성한다. 

     * 가장 부하가 적은 SELECT 1로 설정한다.




    참고 자료 - 

    https://effectivesquid.tistory.com/entry/HikariCP-%EC%84%B8%ED%8C%85%EC%8B%9C-%EC%98%B5%EC%85%98-%EC%84%A4%EB%AA%85

    https://jsonobject.tistory.com/383

    https://preamtree.tistory.com/78

    https://stackoverflow.com/questions/29620265/postgres-connection-has-been-closed-error-in-spring-boot

    https://tomcat.apache.org/tomcat-8.0-doc/jdbc-pool.html#Common_Attributes

    https://d2.naver.com/helloworld/5102792

    1. MySQL은 wait_timeout PostgreSQL은 statement_timeout [본문으로]
    2. This property controls whether the pool will “fail fast” if the pool cannot be seeded with an initial connection successfully. Any positive number is taken to be the number of milliseconds to attempt to acquire an initial connection; the application thread will be blocked during this period. (이 주기 동안에는 애플리케이션의 스레드가 차단된다.) If a connection cannot be acquired before this timeout occurs, an exception will be thrown. (타임아웃이 발생하기 전 커넥션을 획득할 수 없는 경우, 예외를 던진다.) This timeout is applied after the connectionTimeout period. If the value is zero (0), HikariCP will attempt to obtain and validate a connection. (이 타임아웃은 커넥션 타임아웃 주기 후에 적용되며, 만약 값이 0인 경우 HikariCP는 커넥션 검증과 연결을 시도한다.) If a connection is obtained, but fails validation, an exception will be thrown and the pool not started. (커넥션을 얻었으나 검증에 실패했다면 풀이 시작되지 않았음을 의미하는 예외를 던진다. ) However, if a connection cannot be obtained, the pool will start, but later efforts to obtain a connection may fail. (그러나, 커넥션을 얻지 못하면 풀이 시작되지만, 커넥션을 얻는 것에 실패할 것이다.) A value less than zero will bypass any initial connection attempt, and the pool will start immediately while trying to obtain connections in the background. (0보다 작은 값은 커넥션 초기화 시도를 건너뛰고, 백그라운드에서 커넥션을 얻으려는 시도 중에 즉시 풀이 시작된다.) Consequently, later efforts to obtain a connection may fail. Default: 1 [본문으로]
Designed by Tistory.