본문 바로가기
CS

[CS] HikariCP 설정으로 쿼리 성능 개선하기

by doodoom 2023. 10. 7.

0. 이 글을 쓰게된 이유

이번 프로젝트에서 HicariCP 설정을 통한 쿼리 성능 최적화를 담당하게 되었다. 이 과정에서 배운 점을 기록하고자 이 글을 쓰게되었다.

1. Connection pool size

일단 제일 중요한 Connection pool size로 성능 개선을 해보자. HikariCP 공식 문서에서 Connection pool size 설정 관련한 WIKI를 보면 오히려 크기를 늘리지말고 줄이는 것이 성능 개선에 유리하다고 되어있다. 그렇다면 왜 그럴까?

1.1 제한된 서버 자원

데이터베이스의 병목 현상은 주로 CPU, 하드디스크, 네트워크 이 세가지로 인해 발생한다고 한다. 메모리도 물론 영향을 미치지만, 이들에 비해 굉장히 미미한 수준이다. 우리의 서버는 이러한 자원들이 제한되어있으므로, Connection pool도 이에 맞게 설정해야한다. 그 이유에 대해서 알아보자.

1.1.1 CPU

CPU는 컨텍스트 스위칭을 통해 여러 스레드를 동시에 처리하는것처럼 보이게 해준다. 하지만 어쨌든 하나의 CPU core는 하나의 쓰레드만을 처리한다. 만약 쓰레드 개수가 CPU core 개수보다 많다면 하나의 쓰레드가 돌아가고있을때 나머지 쓰레드는 병목 현상이 일어나게된다.

1.1.2 디스크

메모리를 많이 활용한다고해도 데이터베이스의 데이터는 디스크를 사용한다. 알다싶이 디스크 I/O에 대한 비용은 상당히 크다. 물론 캐싱이 많이 도움이 되긴하지만, 그래도 이 사실은 변하지 않는다.

이러한 I/O 시간 동안 connection/thread/query는 단순하게 대기하고, 이는 병목 현상을 일으킨다.

1.1.3 네트워크

네트워크 또한 버퍼를 이용하기 때문에 버퍼 풀이 가득 차면 병목 현상이 발생할 여지가 있다.

이러한 이유들로 Connection pool size는 오히려 늘리게 되면 자원들에 의한 병목 현상 때문에 성능이 나빠지게된다.
HikariCP에서 예시로 진행한 성능 측정에서는 Connection pool size가 2048일때는 한 connection이 queue time이 30ms가 걸리고 run time은 71ms가 걸렸다. 하지만 Connection pool size를 96으로 줄이니 queue time이 1ms, run time이 2ms로 파격적으로 성능이 개선되었다.

1.2 최적의 Connection pool size를 찾는 공식

그렇다면 우리 서비스 서버의 환경에서 최적의 Connection pool size는 무엇일까? HikariCP가 강력하게 추천하는 공식은 다음과 같다.

connections = ((core_count * 2) + Effective_spindle_count)

여기서 core_count는 cpu core의 개수이고, Effective_spindle_count는 효과적인 스핀들(spindle) 수인데, 캐쉬가 완벽히 되어있으면 0이라고 한다. 만약 캐시 히팅률이 0%에 가깝다면 disk의 스핀들 수라고 한다.

즉, 활성화된 연결의 개수가 위 공식에 가까울때 최적의 성능을 보이는 것이다. 내가 운영하는 서버의 cpu, disk spindle 개수를 찾고, 1개씩 늘리고 줄이면서 테스트하면 최적의 상태를 찾을 수 있을 것 같다.

2. 서비스 성능 테스트

이제부터 우리 서비스의 성능 테스트를 진행해보자. 우리의 서버는 AWS EC2 t4g.small이고 aws 인스턴스 사양 공식문서에 따르면 vCPU개수가 2이므로 CPU core 개수는 2라고 해도 될 것 같다. 하지만 spindle은 ec2같은 클라우드 환경에는 적용되는 개념이 아니다. 그러므로 일단 0으로 가정하고 하나씩 늘려가면서 성능 테스트를 해보자! 

 

위의 식에 실제 서버의 사양을 넣으면 다음과 같다.

connections = (2 * 2) + Effective_spindle_count = 4 + Effective_spindle_count

4를 기준점으로 하나씩 늘리면서 테스트를 해보면 될 것 같다!

 

2.1 실제 성능 테스트

테스트 도구 : JMeter

테스트 API : 메인 로드맵 리스트 조회 페이지(메인 페이지에 위치해있고, 무한 스크롤이 적용되어있기 때문에 제일 요청이 많이 들어온다.)

부하 조건 : 1000/sec 

 

maximumPoolSize : 10(default)

평균 처리 시간 : 1896ms(약 1.9초)

최대 처리 사간 : 3940ms(약 4.0초)

TPS : 225.9/sec

 

maximumPoolSize : 4

평균 처리 시간 : 3042ms(약 3초)

최대 처리 사간 : 6243ms(약 6.3초)

TPS : 149.5/sec

 

...? 더 안좋아졌는데..? 심지어 에러도 난다..

 

maximumPoolSize : 6

평균 처리 시간 : 2632ms(약 2.6초)

최대 처리 사간 : 4920ms(약 4.9초)

TPS : 173.0/sec

 

size 4 보다는 13%정도 좋아진것 같지만, default인 10보다는 현저하게 떨어진다. 

 

maximumPoolSize : 8

평균 처리 시간 : 1783ms(약 1.8초)

최대 처리 사간 : 3527ms(약 3.5초)

TPS : 234.4/sec

 

오 이제 에러도 안나고 드디어 default보다 성능이 개선되었다. 대략 6% 정도 좋아졌고, TPS도 10쯤 높아졌다! 거의 근사치를 찾은 것 같다.

 

maximumPoolSize : 9

평균 처리 시간 : 1660ms(약 1.66초)

최대 처리 사간 : 3490ms(약 3.5초)

TPS : 245.2/sec

 

size 8 보다 성능이 더 좋아졌다! default랑 비교해서 평균 처리 시간은 12.45%가 빨라졌고, TPS는 20쯤 높아졌다! 아무래도 최적의 해를 찾은것 같다.

 

3. 결론

우리 서비스의 서버에서는 Connection pool size가 9가 제일 성능이 좋다는 결론이 나왔다. 개선된 성능은 다음과 같다.

평균 처리 시간 속도 개선 : 1896ms -> 1660ms 12.45% 증가

최대 처리 시간 속도 개선 : 3940ms -> 3490ms 12.89% 증가

TPS 개선 : 225.9/sec -> 245.2/sec, 8.89% 증가