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

Jdbc

HikariCP 코드 분석하기 3편(HikariCP 커넥션 풀 초기화 과정 디버깅)

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

HikariDataSource초기화

  • 먼저 아래와 같은 옵션으로 DataSource를 빈으로 등록했다.
    • 생성자에 HikariConfig를 인자로 넘긴다.
    • maximumPoolSize: 3
    • maxLifetime: 10분
  • HikariConfig를 인자로 넘겼기 때문에 fastPathPool도 같이 초기화 하는것을 확인할 수 있다.
  • 이제 HikariPool을 초기화하는 과정을 알아보자.

 

HikriPool 초기화

  • 2편에서와 마찬가지고 아래의 과정으로 나눠보았다.
  • HikariPool을 초기화하는 과정을 아래와 같은 단계로 나눠보았다.
    1. DataSource 생성
    2. connectionBag 생성
    3. houseKeepingExecutorService 생성
    4. 커넥션 연결 확인
    5. 커넥션 유지
    6. 커넥션 추가

 

DataSource 생성

  • HikariPool의 상위타입인 PoolBase에서 DataSource를 생성한다.
  • initializeDataSource() 메소드에서 HikariConfig에 있는 옵션들을 바탕으로 331번째 줄에서 DataSource를 생성한다.

 

connectionBag 생성

  • 실질적으로 Connection Pool역할을 하는 변수다.
  • ConcurrentBag 타입의 변수이며, 아래와 같이 내부적으로 threadList, sharedList, handOffQueue를 가지고 있다.

  • 아래 코드를 보면 threadList, sharedList, handOffQueue를 초기화하는 것을 확인할 수 있다.

 

houseKeepingExecutorService 생성

  • houseKeepingExecutorService는 idle Connection의 수를 주기적으로 체크하는 역할을 한다.

  • houseKeeper라는 이름으로 threadFactory를 만든다.
  • daemon thread를 true 설정했는데, 여기서 데몬 스레드는 주 스레드의 작업을 돕는 보조적 역할을 담당하는 쓰레드로이며 주 스레드가 종료되면 데몬 스레드도 같이 종료된다.
  • 다음으로 ScheduledThreadPoolExecutor 타입으로 houseKeepingExecutorService를 생성하면서 인자로 좀전에 만든 threadFactory를 넘긴다.
  • ScheduledThreadPoolExecutor는 어떤 작업을 주기적으로 실행하거나 일정 시간 지연 후에 수행할때 사용된다.
  • houseKeeper가 주기적으로 idle connection 수를 체크하기 때문에 ScheduledThreadPoolExecutor가 적합하다.
    • scheduleWithFixedDelay 메소드를 사용해서 작업이 완료되고 일정 시간 뒤에 다시 작업을 실행한다.

 

addConnectionExecutor 생성

  • addConnectionExecutor 는 이름 그대로 커넥션을 생성한다.
  • 커넥션을 추가하는 시점에 addConnectionExecutor가 createPoolEntry() 메소드를 호출해서 addConnectionExecutor의 스레드풀의 스레드로 커넥션을 생성한다.
    • 이 부분은 아래에 커넥션을 추가하는 과정 설명에서 좀 더 자세하게 설명한다.

 

커넥션 연결 확인

  • checkFailFast() 메소드를 통해, 연결 확인용으로 1개의 커넥션만 맺어보고, 커넥션 풀에 넣어둔다.
  • createPoolEntry() 메소드를 통해서 1개의 poolEntry를 연결 확인용으로 만들어 본다.
  • 그 뒤, connectionBag에 만들어진 poolEntry를 넣는다.
  • connectionBag의 sharedList를 보면 NOT_IN_USE 상태의 커넥션이 하나 담겨있는 것을 확인 할 수 있다.

 

커넥션 유지

  • 현재 maximumPoolSize를 3으로 설정해서 위에서 커넥션 연결 확인을 위해 1개의 poolEntry를 connectionBag에 넣었기 때문에 connectionToAdd인 것을 확인할 수 있다.
  • 실제로 fillPool() 메소드에 브레이크 포인트를 걸어보면 일정 주기로 fillPool() 메소드가 호출되며, submit의 반환값이 Future인 것을 보면 별로의 스레드의 비동기적으로 커넥션을 채운다는 것을 확인할 수 있다.
  • 그렇다는 것은 커넥션풀이 10이더라도 서버가 구동된다음 커넥션풀이 10개가 될때까지 기다리는 것이 아니라 checkFailFast() 1개의 커넥션만 확인한 다음 나머지 9개는 서버가 뜨면서 비동기적으로 채워진다는 것을 알 수 있었다.
    • houseKeepingExecutorService 가 HouseKeeper를 동작시키는 주체이며, houseKeepingExecutorService를 생성할 때 만들어둔 별도의 스레드풀에서 비동기적으로 실행된다.

  • 이 부분이 이해가 되지 않는다면, java 1.5 버전에 등장한 Future와 ExecutorService를 공부해보면 좋을 것 같다. 참고로 자바 1.8부터 CompletableFuture를 더 많이 사용한다.

 

커넥션 추가

  • 1편에서 설명했듯이, 커넥션이 필요한 시점에 ConcurrentBag.borrow() 메소드에서 커넥션을 반환받는다.
  • borrow() 메소드에서 현재 sharedList에 사용가능한 커넥션이 없다면, 커넥션을 추가한다.
  • addBagItem() 메소드에서 submit() 메소드를 호출해서 비동기적으로 커넥션을 추가하면, addConnectionExecutor가 createPoolEntry()메소드를 통해서 PoolEntry에 커넥션을 담아서 반환해준다.


이번편에서는 HikariPool을 초기화하는 과정을 알아보았다. 다음편에서 커넥션을 점유하고 반납하는 과정에 대해서 알아보자

 

References