본문 바로가기
공부/Spring

HikariCP란 무엇이고 어떤 풀 사이즈를 적용해야 할까?

by JERO__ 2022. 10. 20.

WAS와 DB의 연결

WAS와 DB 사이 연결에는 많은 비용이 든다. 만약 하나의 SQL을 DB에 적용한다면 다음과 같은 과정을 거친다.

  1. JDBC 드라이버 로드
  2. DB 연결
  3. statement 생성 (인터페이스이며 DB에 SQL을 전달해주는 역할)
  4. SQL 전송
  5. ResultSet을 통해 결과 확인
  6. 연결 해제

SQL 쿼리 하나를 전송하기 위해 위와 같은 행동이 반복된다. 이는 매우 비효율적이다. 정리하면 다음과 같다!

  • 전체과정 중 커넥션을 생성하는 과정이 약 50%를 차지한다고 한다.
  • 이러한 연결과정은 TCP/IP를 통해 이루어져 3-way-handshaking 과정을 통해 준비한다.
  • 쿼리를 요청할 때 반복되면 네트워크 구간에서 병목현상이 발생할 수 있다.

이를 어떻게 해결할 수 있을까? 바로 커넥션 풀을 사용하는 것이다.

 

커넥션 풀이란?

WAS와 DB 사이의 커넥션을 이미 맺고 이를 커넥션 풀에서 관리하는 것이다. 이를 통해 네트워크 연결 시간을 단축할 수 있다.

  • 커넥션 개수를 일정 수준으로 제한하여 불필요한 자원을 낭비하면 안된다.
  • 일관된 데이터베이스 성능을 유지할 수 있다.

HikariCP란?

HikariCP는 용량이 작고 빠른 속도를 가진 JDBC 커넥션 풀 프레임워크이다.

SpringBoot의 default 커넥션 풀로 사용된다.

HikariCP는 어떻게 동작할까?

동작과정

  1. Thread 1 이 Connection Pool에 Connection을 요청한다.
  2. 사용할 수 있는 Connection을 찾는다.
  • 있다면 Connection을 반환한다.
  • 없다면 없음을 반환한다.
    • Thread는 HandOffQueue를 Polling한다. (주기적으로 검사)
    • 사용가능한 Connection이 HandOffQueue에 삽입된다.

HikariCP의 기본정보를 알아보자!

  1. maximumPoolSize
    • 풀이 제공할 수 있는 최대 Connection 개수이다. 기본 값은 10개이다.
    • TPS(초당 트랜잭션 개수)에 가장 영향을 준다고한다.
  2. connectionTimeout
    • 커넥션 풀에서 커넥션을 구하기 위해 대기하는 시간이다.
    • 기본값은 30초이다.
    • 보통 웹 서비스는 0.5 ~ 3초 이내로 설정하여 응답 시간을 최소화 한다.
  3. maxLifeTime
    • 커넥션을 생성한 뒤 설정된 시간이 지나면 커넥션을 닫고 풀에서 제거한다. 제거한 뒤 커넥션을 새롭게 생성한다. 기본값은 30초이다.
    • 기본적인 규칙은 네트워크나 데이터베이스의 관련 설정 값보다 작은 값을 사용한다.
    • 최대 TCP 유지 시간이 10분이라고 가정한다. 이 값이 관련 설정보다 크게되면 이미 유효하지 않은 커넥션이 풀에 남게 된다.
  4. keepaliveTime
    • 커넥션이 살아 있는지 확인하는 주기이다. 유휴 커넥션에 대해 커넥션을 검증하고 유효하지 않은 경우 풀에서 제거한다. 제거 후 커넥션을 새로 생성한다.
    • 데이터베이스의 미활동 시간보다 설정 시간이 긴 경우 이미 데이터베이스에서 커넥션을 제거했기 때문에 무의미한 검증 시간이 될 수 있다.
  5. minimumIdle
    • 풀에서 유지할 최소 유휴 커넥션 개수를 지정한다. 기본 값은 maximumPoolSize와 동일하다.
    • HikariCP 문서에 따르면 설정하지 않는 것을 추천한다. 즉 maximumPoolSize와 동일 크기를 추천한다. 이 값을 작게 설정할 경우 급격한 트래픽 증가 시 성능 저하를 일으킬 수 있다.

 

적절한 커넥션 풀 사이즈를 고민해보자

커넥션 풀을 구성하는 것은 개발자가 자주 실수하는 것이다. 풀을 구성할 때 이해 해야하는 몇 가지 원칙이 있을 수 있다.

하나의 CPU 코어가 있는 컴퓨터도 수십 혹은 수백 개의 스레드를 동시에 지원할 수 있다. 하지만 이것은 운영체제의 속임수일 뿐이다. 실제로 단일 코어는 한 번에 하나의 스레드만 실행할 수 있다. 운영체제는 컨텍스트 스위칭을 한 뒤 다른 스레드의 코드를 실행할 뿐이다. 즉 빠른 시간의 컨텍스트 스위칭으로 동시에 진행하는 것 처럼 보일 뿐이다.

단일 CPU가 주어지면 A와 B를 순차적으로 실행하는 것이 시분할을 통해 A와 B를 동시에 실행하는 것 보다 항상 빠를 것이라는 것은 컴퓨팅의 기본 법칙이다. 스레드 수가 CPU 코어 수를 초과하면 단순히 스레드 수가 더 많아질 뿐이지 더 빠른 속도를 보장하는 것은 아니다. 즉 단순히 풀의 크기를 늘린다고 더 빠른 속도로 처리되는 것은 아니다.

데이터베이스의 주요 병목 현상은 다음과 같다.

  • CPU
  • 디스크
  • 네트워크
  • 여기서, 디스크네트워크무시하면 간단히 계산이 가능하다. 8개의 CPU 코어가 있다면, 커넥션 수를 8개로 설정하면 최적일 것이다.
  • DB는 일반적으로 디스크에 저장된다. (아래 과정이 일어나는 동안 스레드는 block 한다)
    • 전통적인 모터 구동 암에 읽기/쓰기 헤드가 장착된 회전 금속 플레이트로 구성된다.
    • 읽기/쓰기 헤드는 한 번에 한 곳에만 읽을 수 있으며 다른 쿼리에 대한 데이터를 읽기 위해서는 새 위치를 검색해야 한다.
    • 탐색 시간 비용과 플래터의 데이터가 다시 돌아오기 까지 디스크를 기다려야 하는 회전 비용이 추가적으로 발생한다.

  • 네트워크는 이더넷 인터페이스를 통해 유선으로 데이터를 작성하면 송/수신 버퍼가 가득차거나 멈출 때 block이 발생할 수 있다.

 

적절한 사이즈 공식을 적용해보자

PostgreSQL에서 제공한 공식

다양한 상황을 고려 했을 때 PostgreSQL에서는 아래와 같은 공식을 제안했다.

여러 데이터베이스에도 적용할 수 있다고 언급되어 있다.

connections = (corecount * 2) + effectivespindle_count
  • core_count * 2: 코어 수에 근접할 수록 좋지만, 위에서 언급한 디스크 및 네트워크와 CPU의 속도차이로 인한 여유 시간을 활용하기 위해 계수 2를 곱해준다.
  • effective_spindle_count: 하드 디스크는 하나의 spindle을 가진다. spindle은 데이터베이스 서버가 관리할 수 있는 동시 I/O 요청 수를 말한다. 디스크가 n개 존재하면 spindle_count는 n이 될 수 있다.

 

참고

https://github.com/brettwooldridge/HikariCP#gear-configuration-knobs-baby

https://code-lab1.tistory.com/m/209

https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html

https://woowabros.github.io/experience/2020/02/06/hikaricp-avoid-dead-lock.html

https://hyuntaeknote.tistory.com/12

https://kafcamus.tistory.com/47

https://dallog.github.io/hikari-cp-theory/

댓글