데이터베이스 커넥션 풀(HikariCP) 설정 최적화와 성능 튜닝 사례

지금까지의 시리즈를 통해 JVM 내부의 메모리 구조, 가비지 컬렉션(GC), 멀티 스레딩, 그리고 운영체제 레벨의 모니터링까지 서버 자체의 성능을 끌어올리는 방법들을 깊이 있게 살펴보았습니다. 하지만 아무리 서버의 CPU와 메모리를 최적화하더라도, 애플리케이션 바깥에 있는 ‘데이터베이스(DB)와의 통신 병목’을 해결하지 못하면 전체 시스템의 성능은 결코 나아지지 않습니다.

현재 Spring Boot 환경에서 가장 널리 쓰이는 표준이자, 극강의 성능을 자랑하는 커넥션 풀(Connection Pool) 라이브러리가 바로 HikariCP입니다. 이번 글에서는 커넥션 풀의 기본 원리와 함께, 실무에서 대규모 트래픽을 견뎌내기 위해 HikariCP를 어떻게 튜닝해야 하는지 구체적인 설정값과 최적화 사례를 알아보겠습니다.

1. 커넥션 풀(Connection Pool)이 왜 필요할까?

데이터베이스와 통신하기 위해 물리적인 연결(Connection)을 맺는 과정은 생각보다 엄청난 비용이 듭니다. 네트워크를 통한 TCP 3-way Handshake를 거치고, DB 서버의 인증(Authentication)을 통과해야 비로소 쿼리를 날릴 수 있는 통로가 열립니다.

만약 사용자의 요청이 들어올 때마다 매번 이 연결을 새로 맺고 끊는다면(Connection Per Request), 응답 시간은 기하급수적으로 늘어납니다. 그래서 애플리케이션을 구동할 때 미리 일정 개수의 연결(Connection)을 맺어두고 수영장(Pool)처럼 모아두었다가, 요청이 올 때마다 빌려주고 다시 반납받는 방식을 사용합니다. 이것이 커넥션 풀의 핵심이며, 데이터베이스 튜닝의 가장 첫 단추입니다.

2. 풀 사이즈(Pool Size), 크다고 무조건 좋을까?

개발자들이 가장 많이 하는 실수 중 하나는 “트래픽이 많으니 커넥션 풀 사이즈를 100개, 500개로 넉넉하게 잡아야지”라고 생각하는 것입니다. 이는 데이터베이스 성능을 갉아먹는 치명적인 오판입니다.

데이터베이스는 하드 디스크(혹은 SSD)에 접근(I/O)하여 데이터를 가져와야 합니다. 하나의 디스크는 한 번에 하나의 작업만 수행할 수 있습니다. 수백 개의 커넥션이 동시에 쿼리를 던지면, DB 서버 내부에서는 엄청난 컨텍스트 스위칭(Context Switching) 비용이 발생하고 락(Lock) 경합이 심해져 오히려 처리 속도가 급격히 떨어집니다.

💡 PostgreSQL 공식 위키에서 권장하는 공식

최적의 커넥션 수 = ((Core Count * 2) + Effective Spindle Count(하드디스크 수))

즉, 코어가 4개인 DB 서버라면 커넥션 풀 사이즈는 10개 전후면 충분합니다. 대량의 DB 쿼리 결과를 조회하여 JSON 파일로 변환하는 등의 무거운 작업을 수행할 때도, 무작정 풀을 늘리기보다 이 공식을 기준으로 점진적인 부하 테스트를 통해 최적값을 찾아야 합니다.

3. 실무 환경의 필수 HikariCP 튜닝 옵션

Spring Boot의 application.yml 파일에서 설정할 수 있는 HikariCP의 핵심 옵션들은 다음과 같습니다.

  • maximum-pool-size: 풀에 유지할 수 있는 최대 커넥션 수입니다. 위에서 언급한 공식을 참고하여 설정하며, 너무 높게 잡지 않도록 주의합니다. (HikariCP의 기본값은 10입니다.)
  • minimum-idle: 아무런 요청이 없을 때 최소한으로 유지할 유휴(Idle) 커넥션의 수입니다. HikariCP 제작자는 예측할 수 없는 트래픽 스파이크에 대비해 maximum-pool-size와 동일한 값으로 설정하여 풀의 크기를 고정(Fixed)하는 것을 강력히 권장합니다.
  • connection-timeout: 스레드가 커넥션을 빌리기 위해 대기하는 최대 시간입니다. (기본값 30,000ms = 30초). 30초는 실무 웹 서비스에서 너무 깁니다. 차라리 3~5초(3000~5000ms) 정도로 짧게 설정하여, 커넥션을 얻지 못하면 빠르게 타임아웃 에러를 발생시키는 것(Fail-fast)이 서버 전체의 스레드 고갈(장애 전파)을 막는 현명한 전략입니다.

4. 실전 최적화: 커넥션 누수(Leak) 방지

설정값을 완벽하게 맞춰도, 코딩 실수로 인해 사용한 커넥션을 풀에 반납(close())하지 않으면 풀이 고갈되어 서버가 멈춥니다.

HikariCP는 이를 감지하는 훌륭한 기능을 제공합니다. leak-detection-threshold 옵션을 활성화(예: 2000ms)해 두면, 특정 커넥션이 2초 이상 반납되지 않고 점유되어 있을 때 경고 로그(WARN)를 남겨줍니다. 트랜잭션이 비정상적으로 길어지거나 커넥션이 누수되는 로직의 위치를 찾아낼 수 있는 아주 유용한 실무 팁입니다.

결론: 데이터베이스를 괴롭히지 말 것

커넥션 풀 최적화의 핵심은 ‘애플리케이션 스레드가 빠르게 DB 자원을 쓰고 제자리에 돌려놓게 만드는 것’입니다. 아무리 풀을 잘 설정해도 개발자가 비효율적인 쿼리를 던져 하나의 커넥션을 오래 물고 있다면 병목은 해결되지 않습니다.

그래서 다음 포스팅에서는 애플리케이션 레벨에서 발생하는 가장 대표적인 데이터베이스 병목 원인인 ORM 기술의 부작용, ‘JPA/Hibernate N+1 문제 진단 및 실무에서의 쿼리 성능 개선 전략’에 대해 심도 있게 분석해 보겠습니다.

댓글 남기기