Hikari CP 간단한 튜닝

오픈 예정인 서비스에서 아래 와 같은 오류가 발생했다.

cannotCreateTransactionException

Could not open JPA EntityManager for transaction; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection

보자마자 Hikari CP 문제 라고 생각이 들었다.

서비스 개발 전에 이미 우아한형제들 기술블로그에서 이와 관련된 컬럼을 봤었기에 알 수 있었다.

첫번째 목표는 서비스 다운, 즉 Dead Lock 을 피하는것이 우선이였다.

그 다음이 속도, 효율 등을 목표로 생각했다.

Hikari CP 는 스프링이 기본으로 지원하는 쓰레드 풀로 사용되고 있는 오픈소스이다.

      connection-timeout: 30000  # 커넥션 타임아웃 설정 (ms)
      maximum-pool-size: 10  # 커넥션 풀의 최대 크기 설정
      minimum-idle: 5  # 유휴 커넥션을 유지할 최소한의 커넥션 수 설정
      idle-timeout: 600000  # 유휴 커넥션의 최대 유지 시간 설정 (ms)
      pool-name: MyHikariCP  # 커넥션 풀의 이름 설정
      validation-timeout: 5000  # 커넥션 유효성 검사 타임아웃 설정 (ms)
      auto-commit: true  # 자동 커밋 모드 설정

위와 같은 기본 설정을 튜닝할 수 있다.

목적에 맞춰서 조절해야할 설정 정보를 아래와 같이 선택했다.

 connection-timeout: 30000  # 커넥션 타임아웃 설정 (ms)
 maximum-pool-size: 10  # 커넥션 풀의 최대 크기 설정

커넥션 타임아웃은 기존 값이 30000 (30초) 로 설정 되있었다.

다만 커넥션 시 아무리 길어도 딜레이는 5초 이상 넘어가는 일이 없었기에 여유를 잡아 10초 이상 넘어갔다면 이미 사실상 타임아웃이기 때문에 굳이 30초 이상 딜레이 시킬 필요가 없다 판단하여 20초로 변경하였다.

아직은 정확한 부하 테스트를 거친것이 아니기에 여유롭게 잡는것이 가장 안전한 방법이라 생각했다.

가장 고민인 부분이 최대 Pool 사이즈를 조절하는 것 이였다.

물론 무조건적으로 히카리풀 사이즈를 조절하는것 보다 애플리케이션 안에서의 문제점을 체크한 다음

풀 사이즈를 조절하는것이 옳지만 현재 오픈을 앞두고 있는 소스를 건드리는것은 조금 무리가 있어

이와 같은 선택을 하였다.

동시 접속자 수, 초당 처리량 을 고려해서 접속자 수에 맞게 설정하면 되는건가? 라는 의문이 가장 컸다.

그럼 접속자 수 가 10000명이면 Pool 사이즈도 그와 같을까?

아래 출처에 남긴 컬럼에서 의문점이 해소 되었다.

컬럼에선 동시접속자 수 10000명, 초당 요청 20000건의 웹사이트에서 커넥션 풀 사이즈를 늘리기보다 100개 정도로 줄였을때 더 빠른 속도로 개선되었음을 보여준다.

각 PC 마다 CPU 가 다르고 CPU 마다 코어 갯수가 다르기에 마냥 풀 사이즈를 늘릴 수 는 없다.

보통 CPU를 보면 코어갯수가 8개면 쓰레드 수 16개 라고 나와있다.

실제로 코어는 한번에 하나의 쓰레드, 프로세스를 처리 할 수 있다. 다만 이것을 빠르게 번갈아가면서 처리하다보니 우리들 눈에는 동시에 처리하는 것 처럼 느끼는 것이다.

이런식으로 처리하는 쓰레드, 프로세스를 바꾸는것을 Context Switching 이라고 한다.

이런 교체 동작이 많아질수록 오버헤드가 발생할 확률이 높다.

고로 코어 개수 역시 풀 사이즈를 정하는데에 있어 고려사항 중 하나 이다.

커넥션 풀의 크기를 유지하는데 드는 비용도 생각해야 한다는 것이다.

보통 일반적인 Pool 사이즈는 (코어 갯수 * 2) + 여유분 이라 하는데 그 이유는 일반적으로 코어 갯수 * 2 가 처리 가능한 쓰레드 수 이기 때문이다.

연결을 기다리는 커넥션으로 가득 찬 "작은" 풀 이 필요하다 라고 컬럼에서 얘기한다.

너무 크면 코어가 감당하지 못할 뿐 더러 효율성이 떨어지고 너무 작다면 코어의 성능을 모두 끌어내지 못할것이기 때문이다.

코어 갯수, 한 요청당 필요한 커넥션의 개수, 초당 요청량 을 고려하여야 한다.

풀 크기 = T n x (C m - 1) + 1

위 공식은 Dead Lock 을 피하기 위한 최소의 풀 사이즈를 구하는 공식이다.

Tn 은 쓰레드 수, Cm 은 요청 당 필요한 커넥션 수 이다.

쓰레드가 10개 라고 할때 요청 당 커넥션 수는 1 이다.

결과는 1이나온다. 고로 10개 쓰레드가 DeadLcok 없이 운용될려면 10라는 풀사이즈가 필요하다고 나온다. maximum-pool-size 의 기본값이 10 인데 이때도 데드락이 발생했으므로 더 안전하게 더 여유있게 가져가고 싶은 마음이 있어서 우아한 형제들에서 기본 공식을 확장시킨 공식을 사용했다.

Tn x (Cm-1) + (Tn/2)

위 공식의 결과는 쓰레드 개수 10 기준으로 5가 나온다..

현재 가용 중인 쓰레드 수 는 60개 정도 이다.

해당 공식을 바탕으로 Pool Size 는 30으로 설정했다.

출처

https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing

https://techblog.woowahan.com/2663/

Last updated