성능을 최적화 해보자 - HikariCP 편

Hyunta·2022년 11월 5일
3
post-custom-banner

ConnectionPool

커넥션풀이란 미리 Connection을 생성하고 보관하는 방법이다. 요청마다 Connection을 생성하게 되면 응답 시간이 길어진다. DB와 네트워크 연결하는 시간을 단축하게 되면, 사용자에게 응답하는 시간이 줄어들고 처리량이 증가한다. 게다가 DB에 대한 커넥션 개수를 일정 수준으로 제한하여 DB 포화를 방지하고 일관된 성능을 기대할 수 있다.

Spring 환경에서는 DataSource 인터페이스를 활용해 데이터 소스와 연결한다. DataSource를 사용하게 되면 application.yml 설정을 통해 DB 연겨릉ㄹ 변경할 수 있다. ConnectionPool 또한 DataSource를 통해서 사용 가능하다.

SpringBoot 2.0부터 HikariCP를 기본 데이터 소스로 채택하고 있다. HikariCP는 가볍고 성능이 매우 빠르기 때문에 가능하면 HikariCP를 채택한다.

ConnectionPool이 무작정 많으면 요청이 많아지더라도 대응할 수 있으니 성능이 좋아질거라 기대할 수 있다. 하지만 Connection 수가 많아지게 된다고 항상 좋은 성능을 내는 것은 아니다. 자료
마치 4개의 Thread만 사용하는 nginx가 100개의 프로세스를 띄워놓는 apache보다 성능이 뛰어난 것처럼 다다익선이 아니다. CPU Core는 한번에 하나의 쓰레드만 실행 가능하다. OS가 context를 switch해서 다른 코어가 쓰레드를 처리하게 되는 것이다. contextSwitching도 비용이기 때문에 쉽게 예측할 수 없다.

Hikari ConnectionPool 튜닝

1. 최대 커넥션 개수 - maximumPoolSize

가장 중요한 설정이다. ConnectionPool이 제공할 수 있는 최대 커넥션 개수를 말한다. 이는 사용중인 Connection과 유휴 커넥션을 합친 값을 의미한다.

설정하기 전에 목표 TPS 값이 필요하다.
단순 계산식으로 아래처럼 계산할 수 있다.

최대 TPS = 1개의 커넥션의 초당 처리 요청 개수 * 동시 커넥션 개수
동시 커넥션 개수
 = 최대 TPS / 1개 커넥션의 초당 요청 처리개수
 = 최대 TPS / (1초 / 쿼리 실행 시간)

예시

  1. 목표 TPS 100
  2. 요청이 실행하는 쿼리 총 실행 시간이 0.1초

동시에 필요한 커넥션 개수를 단순 계산하면

  • 동시 커넥션 개수 = 100 / (1 / 0.1 ) = 10 개다.
  • 최대 커넥션 개수가 10개고 한 요청에 평균 0.1초가 소요되면 100 TPS를 처리할 수 있다.

최대 개수 고려사항

SlowQuery를 잡아야한다. 평균 이상으로 실행 시간이 튀는 개수나 비율 검토.

쿼리 실행시간이 0.1이 평균인데 1초 걸리는 쿼리가 순간적으로 5개가 발생하면?

  • 1초 / 1초 * 5개 : 5 TPS
  • 1초 / 0.1초 * 5개 : 50 TPS

TPS가 100에서 55로 줄어든다.
따라서, 느린 쿼리를 염두하고 최대 개수를 높여야 한다. 물론 부하테스트를 진행하면 조금 더 정확한 값을 얻어낼 수 있다.

우리는 부하테스트를 진행해서 최적값을 찾았다.
테스트 환경

  • AWS EC2 t4g.micro : vCPU 2, Memory 1GB
  • jMeter : Thread 150, Loop Count 250

maxPoolSize = 9 / TPS 316

maxPoolSize = 10 / TPS 368

maxPoolSize = 50 / TPS 336

2. 최대 커넥션 대기시간 - ConnectionTimeOut

풀에서 커넥션을 구하기 위해 대기하는 시간이다. 최대 커넥션만큼 사용 중일 때 커넥션을 가져오려고 했을 때 대기하는 시간이다. 기본값은 30초인데 너무 길다.

사용자는 응답없는 상태로 30초를 기다려야한다. 따라서 기본 값 대신에 0.5~3초 이내로 설정하는 것을 권장한다. 응답이 없는 것보다는 빨리 에러 화면이라도 응답해주는게 낫기 때문이다.

3. 커넥션 최대 유지 시간 - maxLifeTime

커넥션을 생성한 이후에 이 시간이 지나면 커넥션을 닫고 풀에서 제거한 뒤 커넥션을 새로 생성한다.

  • 기본 규칙
    • 네트워크나 DB 관련 설정 값보다 작은 값 사용
      ex) 네트워크 장비의 최대 TCP 커넥션 유지 시간

만약, 이 값이 관련 설정보다 크면 이미 유효하지 않은 커넥션이 풀에 남게 된다. 요청이 유효하지 않은 커넥션을 받아오면 유효성 검증과중 중에 커넥션을 새로 생성한다. 트래픽 몰리는 시점일 경우 성능이 저하된다.

예시

DB의 TCP 유지시간이 10분인데 maxLifeTime을 15분으로 주게되면 ConnectionPool에서는 해당 커넥션이 5분동안 유효하지 않은 상태로 살아있게 된다. 해당 Connection을 사용할 때 검사하고 유효하지 않은 경우 커넥션을 생성한다.

4. 커넥션 확인 주기 - keepAliveTime

커넥션이 살아있는지 확인하는 주기

  • Idle 커넥션에 대해 커넥션 확인

  • 유효하지 않은 커넥션 풀에서 제거

  • 제거하 뒤 커넥션을 새로 생성

  • 기본 규칙

    • 네트워크나 DB의 관련 설정값보다 작은 값 사용
      ex) DB의 미활동 커넥션 대기 시간

예를 들어 DB에서 10분동안 안쓰는 커넥션을 끊는다면 그것보다 작은 값을 사용하자.

5. 최소 유휴 커넥션 - minimumIdle

풀에서 유지할 최소 유휴 커넥션 개수
기본값은 maximumPoolSize와 동일하고 HikariCP는 이 값을 바꾸지 않는 것을 권장한다.
용도는 트래픽 적은 시간대 DB 자원 사용을 주링기 위함이다.

개인적으로 모호한 부분이었는데, 이름이 minimumIdle이라 해서 혼동이 있었다.
1. maximumPoolSize가 10이고 minimumIdle이 5라면 처음에는 5개의 커넥션만 만들어진다.
2. 동시에 요청이 4개가 오면 idleConnection의 개수는 1개가 된다.

minimulIdle은 요청이 없을 경우 유지하는 connection을 말하는 것 같다. 개인적으로 minimumConnection이 더 나은 워딩이라고 생각한다.

6. 최대 유휴 시간 - idelTimeOut

사용되지 않고 풀에 머무를 수 있는 시간
풀에서 이 시간동안 머무른 커넥션은 종료하고 풀에서 제거
minimuIdle을 따로 설정했을 경우에 적용
이 시간이 지났다고 바로 빠지지는 않는다. 문서에서는 15초정도 시간이 걸린다고 한다.

  • 기본 규칙
    • 트래픽이 빠지는 시간 간격

정리

중요도 순서대로 나열한 Hiakri ConnectionPool 설정이다. 우리는 실제로 maximumPoolSize와 ConnectionTimeOut 값만 설정했으며 이때 최적의 성능을 보여줬다.

Reference

https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#data.sql.datasource.connection-pool
https://www.baeldung.com/hikaricp
https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing
https://www.youtube.com/watch?v=_C77sBcAtSQ
https://youtu.be/6Q7iRTb4tQE
https://www.baeldung.com/spring-boot-hikari
https://github.com/brettwooldridge/HikariCP

profile
세상을 아름답게!
post-custom-banner

0개의 댓글