모르지 않다는 것은 아는것과 다르다.

Jdbc

HikariCP 코드 분석하기 1편 (HikariCP란?)

채마스 2023. 1. 21. 23:30

개요

  • 지금까지 Hikari DataSource를 사용하면서, HikariCP에서 제공하는 maximumPoolSize, connectionTimeout, maxLifetime, idleTimeout 과 같은 설정값 정도만 알고 있었지 내부적으로 HikariCP가 어떤식으로 Connection Pool을 관리하는지 알지 못했다.
  • 또한, 언젠가 Connection Pool에서 장애가 발생할 수 있을 것을 대비해서 이번 기회에 HikariCP 코드를 분석해 보려고 한다.

Connection Pool이란?

  • 커넥션 풀이란 데이터베이스와 연결된 커넥션을 미리 만들어 놓은 Pool을 의미한다.
  • 만약 커넥션 풀이 없다면?

  • 위와 같이 WAS 내부에 DB 드라이버와 DB가 직접 통신하며 Connection을 가져 와야 된다.
  • 하지만 Connection을 맺는 작업은 상당히 무거운 작업이다.
  • 그 이유는 DB 드라이버와 DB 간에 TCP/IP 통신을 맺어야 하고, 그 과정에서 3 way handshake 와 같은 네트워크 동작들이 수반되기 때문이다.
  • 그 뿐만 아니라 username, password를 바탕으로 인증 처리도 해야되고, 인증이 끝난 후 DB는 별도의 세션도 만들어야한다.
  • 위의 과정들이 다 끝나야 비로소 DB 드라이버는 Connection객체를 만들어 전달할 수 있다.
  • 만약 커넥션 풀이 없고 매번 위의 과정대로 Connection을 직접 만든다면, WAS, DB 모두 부하가 걸리고 이는 장애로 이어질 것이다.
  • 커넥션 풀이 있다면?

  • 위와 같이 DB에서 미리 커넥션을 맺어 Connection Pool에 담아두고 Connection을 DB로 반환하지 않고 재활용해서 사용한다.
  • 따라서 커넥션이 필요한 시점에 DB로 부터 Connection을 가져오는 것이 아니라 Connection Pool로부터 Conneciton을 받아온다.
  • 이렇게 되면, Connection을 새로 만드는 비용을 절약할 수 있을 뿐 아니라 DB에도 부하를 줄일 수 있기 때문에 성능적으로 많은 이득을 얻을 수 있다.

 

Connection Pool에 대한 간단히 개념을 정리 했으니 이제부터 본격적으로 HikariCP가 무엇인지 정리해 보자

 

HikariCP란?

  • Spring에서 DataSource는 Connection pool을 관리하고 연동할 수 있게 하는 표준 인터페이스다.
  • HikariCP는 SpringBoot2.0부터 DataSource의 Default 구현체로 채택되었다.
  • hikariCP는 다른 구현체보다 더 높은 성능을 가지고 있다.

HikariCP 옵션

  • connectionTimeout
    • 클라이언트가 pool에 connection을 요청하는데 기다리는 최대시간을 말한다.
    • default 는 30000(30초) 이다.
    • 설정한 시간을 초과하면 SQLException이 발생한다.
    • 최소 연결 시간은 250ms 이다.
  • maximunPoolSize
    • 유휴 및 사용중인 connection을 포함해서 pool에 보관할 수 있는 최대 커넥션 수를 말한다.
    • default 는 10 이다.
    • 사용할 수 있는 커넥션이 없다면, connection Timeout 시간 만큼 대기한다. -> 시간을 초과한다면 SQLException 이 발생한다.
  • minimumIdle
    • pool 에서 유지가능한 최소 커넥션 갯수를 말한다.
    • default 는 maximumPoolSize 와 동일하다.
    • 특별한 이유가 없다면 설정하지 않는 것이 성능적인 측면에서 좋다.
  • idleTimeout
    • pool에 유휴 상태로 유지시킬 수 있는 최대 시간을 말한다.
    • default는 600000(10분) 이다.
    • minimumIdle이 maximumPoolSize보다 작은 경우에만 사용 가능하다.
    • pool에 있는 connection 수가 minimumIdle에 도달했을 때, 이후에 반환되는 connection에 대해서 바로 반환되지 않고, idleTimeout 만큼 유휴 상태로 있다가 폐기된다.
  • maxLifeTime
    • connection의 최대 유지 시간을 말한다.
    • default는 1800000(30분) 이다.
    • connection이 maxLifeTime 만큼 지났을 때, 사용중인 connection은 바로 폐기되지 않고, 작업이 완료되면 폐기된다. -> 하지만 유휴 커넥션은 바로 폐기된다.
  • readOnly
    • pool에서 얻은 connection이 기본적으로 readOnly로 설정될지를 결정한다.
    • default는 false 이다.
    • 물론 데이터베이스가 readOnly 속성을 지원할 경우에만 사용할 수 있다.
  • connectionTestQuery
    • 데이터베이스 연결이 여전히 활성화 되어있는지 확인하기 위한 쿼리이다.
    • JDBC4 드라이버를 지원한다면 이 옵션은 설정하지 않는 것을 추천한다.
  • poolName
    • Connection Pool의 이름을 지정할 수 있다.
  • driverClassName
    • HikariCP는 기본적으로 jdbcUrl을 참조하여 자동으로 driver를 설정하려고 시도하지만, 몇몇 오래된 driver들은 driverClassName를 표시해 줘야 하는 경우가 있다.
  • validationTimeout
    • 커넥션이 유효한지 검사할 때 사용되는 timeout이다.
    • default는 5000(5초)이다.
    • 최소값은 250ms이다.
  • leakDetectionThreshold
    • 커넥션이 누수 로그메시지가 나오기 전에 커넥션을 검사하여 pool에서 커넥션을 내보낼 수 있는 시간이다.
    • 0으로 설정하면 사용하지 않는 것으로 간주한다.
    • 최솟값은 2000이다.

 

HikariCP 의 Connection Pool 동작 과정

  • 뒤에서 좀더 자세하게 설명 하겠지만 간단히 동작과정을 정리해 보면 아래와 같다.

Connection Pool 구성 요소

  • 먼저 Connection Pool의 구성요소를 간단하게 정리해 보면 아래와같이 3가지로 나뉜다.

  • threadList: Thread가 사용한 Connection의 이력을 남긴다. -> 추후에 캐시와 비슷한 역할로 사용된다.
    • 사용한 이력이 있으면 사용했던 커넥션을 빠르게 반환해 준다. (물론 해당 커넥션 상태가 idle인 경우에만 해당된다.)
  • sharedList: 실제로 Connection을 가지고 있는 공간이다.
  • handOffQueue: sharedList에 사용할 Connection이 없어서 Thread가 Connection을 기다릴 때 handOffQueue 앞에서 대기한다.
    • 다른 Thread에서 Connection을 반환할 때, handOffQueue 앞에서 대기하고 있는 Thread가 있다면 handOffQueue로 Connection을 반환해서 대기하고 있는 Thread가 좀 더 빠르게 Connection을 획득할 수 있다.

 

Connection 획득 과정

  • 1번 과정: 위와 같이 Thread가 Connection을 요청한다면 HikariCP는 먼저 ThreadList를 검사해서 해당 Thread가 사용했던 Connection 이력이 있다면, 해당 Connection을 먼저 전달해준다. (Connection 상태가 idle인 경우만 해당된다.)
  • 2번 과정: 만약 ThreadList에서 Connection을 반환해주지 못했다면, SharedList에서 줄 수 있는 Connection이 있는지 검사하고 있으면 전달해준다.
  • 3번 과정: 만약 SharedList에도 Connection을 반환해주지 못했다면, handOffQueue앞에서 대기한다. -> connectionTimeout 시간 내에 다른 Thread에서 Connection을 반환해주면 handOffQueue에서 connection을 반환 받고, connectionTimeout 시간이 지나도록 Connection을 반환받지 못했다면 예외를 던진다.

 

Connection 획득 과정 예시 #1

가정: Thread1이 C1를 사용한 이력이 있고, 현재 Thread2가 C1, C3를 점유하고 있다. 또한, 여기서 Thread1이 Connection을 요청한다.

  • 먼저 ThreadList를 검사한다. -> Thread1이 c1을 사용한 이력이 있지만 Thread2가 c1을 사용중이기 때문에 패스한다.
  • 그 다음 SharedList를 검사한다. -> c2가 사용가능 함으로 c2를 반환한다.

 

Connection 획득 과정 예시 #2

가정: Thread1이 C1를 사용한 이력이 있고, 현재 Thread2가 C1, C2, C3를 점유하고 있다. 또한, 여기서 Thread1이 Connection을 요청한다.

  • 먼저 ThreadList를 검사한다. -> Thread1이 c1을 사용한 이력이 있지만 Thread2가 c1을 사용중이기 때문에 패스한다.
  • 그 다음 SharedList를 검사한다. -> SharedList에도 사용가능한 Connection이 없으니 패스한다.
  • 마지막으로 Thread1이 handOffQueue에서 Thread2가 반환한 c2를 반환 받는다.

 

Connection 반환 과정

가정: Thread1이 Connection을 반환 하려고 한다. 또한, 현재 handOffQueue앞에 대기하고 있는 Thread는 없다.

  • 먼저 Connection의 상태를 active -> idle로 바꾼다.
  • handOffQueue 앞에 대기하고 있는 Thread가 없기 때문에 ThreadList에 이력을 남기고 반환한다.

가정: Thread1이 Connection을 반환 하려고 한다. 또한, 현재 Thread2가 handOffQueue앞에 대기하고 있다.

  • 먼저 Connection의 상태를 active -> idle로 바꾼다.
  • handOffQueue 앞에서 Thread2가 대기하고 있기때문에 Connection을 handOffQueue로 반환하고, 대기하고 있던 Thread2가 Connection을 반환받는다.

 

이제까지 간단한 개념들을 정리해 보았으니, 다음편부터 본격적으로 HikariCP 내부 코드를 까보도록 하자

 

References