포스트

DBCP (Database Connection Pool)

HikariCP의 minimumIdle·maximumPoolSize·maxLifetime이 DB의 max_connections·wait_timeout과 어떻게 맞물리는지, 적절한 커넥션 수를 부하 테스트로 찾는 흐름.

DBCP (Database Connection Pool)

커넥션 풀을 잘못 잡으면 그날 밤 장애 알림이 온다. 풀 사이즈를 너무 작게 잡으면 트래픽이 몰릴 때 스레드들이 커넥션을 얻지 못해 대기하다가 connectionTimeout에 걸려 요청이 줄줄이 실패한다. 반대로 무작정 크게 잡으면 DB의 max_connections를 넘겨 접속 자체가 거부되거나, DB 서버가 과도한 동시 커넥션에 컨텍스트 스위칭·락 경합으로 오히려 느려진다. 즉 커넥션 풀은 “많을수록 좋은” 값이 아니라 앱과 DB 사이에서 맞춰야 하는 균형점이다.

매번 새로운 Database Connection을 생성하는 것은 TCP 핸드셰이크·인증·세션 초기화를 매번 치르는 비싼 작업이다. 그래서 Connection Pool은 커넥션을 미리 만들어두고 재사용한다. 이 글에서는 HikariCP의 풀 설정(minimumIdle·maximumPoolSize·maxLifetime)이 DB의 max_connections·wait_timeout과 어떻게 맞물리는지, 그리고 적절한 커넥션 수를 어떻게 찾는지를 정리한다.

Database 설정 (MySQL)

  • max_connections : DB가 허용하는 최대 동시 접속 수다. 앱 인스턴스가 여러 대라면 (인스턴스 수 × maximumPoolSize)가 이 값을 넘지 않아야 한다. 넘기면 Too many connections 에러로 접속이 거부된다.
  • wait_timeout : 커넥션이 idle 상태로 아무 쿼리도 받지 않을 때 DB가 얼마를 기다린 뒤 그 커넥션을 끊을지를 결정한다. 이 값이 뒤에서 maxLifetime과 직접 맞물린다.

DBCP(Database Connection Pool) 설정, HikariCP

  • minimumIdle : pool이 유지하려는 최소 idle(사용되지 않는) connection 수다.
  • maximumPoolSize : pool이 가질 수 있는 최대 connection 수로, idle과 active(in-use)를 합친 값이다. idle connection 수가 minimumIdle보다 작으면서 전체 connection 수가 maximumPoolSize보다 작을 때 새 connection을 만들어 pool에 추가한다. 즉 maximumPoolSize가 상한으로서 minimumIdle보다 우선한다.
  • maxLifetime : pool 안에서 connection이 살아있을 수 있는 최대 시간이다. 이 시간을 넘기면 idle일 경우 즉시 pool에서 제거하고, active일 경우 pool로 반환된 직후 제거한 뒤 새 connection으로 교체한다. 오래된 커넥션을 주기적으로 갈아끼워 DB가 몰래 끊어버린 죽은 커넥션을 잡고 있는 상황을 예방한다.
  • connectionTimeout : pool에서 connection을 얻기 위해 기다리는 최대 시간이다(무한 대기 방지). 이 시간 안에 빈 커넥션이 생기지 않으면 예외를 던진다.

HikariCP는 minimumIdle의 기본값을 maximumPoolSize와 동일하게 두고, 이 값을 그대로 쓰는 것(고정 크기 풀)을 권장한다. idle 커넥션 수를 별도로 낮게 잡아 풀을 늘였다 줄였다 하면 트래픽이 튀는 순간 커넥션 생성 비용이 지연으로 드러나기 때문에, 최대치만큼 미리 만들어 고정해두는 편이 응답 시간이 안정적이다.

풀 사이징 — 작을수록 빠르다

HikariCP 위키의 핵심 주장은 반직관적이다. 커넥션은 늘린다고 처리량이 좋아지지 않는다. DB는 결국 CPU 코어 수와 디스크 스핀들 수만큼만 실제로 병렬 작업을 한다. 그보다 많은 커넥션은 DB 안에서 컨텍스트 스위칭과 락 경합만 늘려 오히려 전체 처리량을 떨어뜨린다.

위키가 오랜 벤치마크에서 잘 들어맞았다고 소개하는 출발 공식은 다음과 같다.

1
connections = (core_count * 2) + effective_spindle_count
  • core_countDB 서버의 물리 코어 수다. 하이퍼스레딩 논리 코어는 세지 않고, 앱 인스턴스의 코어가 아니라 DB 서버 기준이다.
  • effective_spindle_count는 디스크가 동시에 감당할 수 있는 랜덤 I/O 수에 해당한다. 데이터셋이 캐시에 완전히 올라와 있으면 0에 가깝고, SSD는 사실상 1로 본다.

예를 들어 물리 4코어에 SSD 한 개인 DB라면 (4 * 2) + 1 = 9, 넉넉하게 반올림해 10 정도가 출발점이다. 웹 앱에서 흔히 보는 수백짜리 풀은 거의 항상 과하다. 물론 이 공식은 절대 진리가 아니라 부하 테스트로 실측하기 전의 시작점일 뿐이다.

maxLifetime은 DB wait_timeout보다 몇 초 낮게

maxLifetime은 DB나 그 앞단 인프라(로드밸런서, 프록시)가 커넥션을 강제로 끊는 시간보다 몇 초 짧게 잡아야 한다. MySQL/MariaDB에서 maxLifetimewait_timeout보다 크면, DB가 이미 닫아버린 커넥션을 HikariCP가 살아있는 줄 알고 꺼내 쓰다가 에러가 난다.

HikariCP의 maxLifetime 기본값은 30분(1,800,000ms)이다. 만약 DB의 wait_timeout이 이보다 짧다면(예: 10분) maxLifetime을 반드시 그 아래로 내려야 한다. 예컨대 wait_timeout이 600초(10분)라면 maxLifetime은 570,000ms(9.5분)처럼 30초~몇 분의 여유를 두고 낮춰 잡는다.

DB 설정과 DBCP 설정은 서로 물려 있으니 반드시 함께 조정해야 하고, 두 설정을 맞춰가며 적절한 connection 수를 찾아야 한다.


적절한 connection 수 찾는 방법

  1. 모니터링 환경 구축 (서버 리소스, 서버 스레드 수, DBCP 등)
  2. 백엔드 시스템 부하 테스트 (JMeter, Gatling, nGrinder 등)
  3. request per second (RPS), average response time (ART) 등을 측정하여 적절한 connection 수를 찾음

서버 부하 상황 별 대응 방법

  • 백엔드 서버, DB서버의 CPU, Memory, Disk I/O, Network I/O 등의 리소스가 부족한 경우
    • 리소스 추가 (ex> 서버 추가, DB서버 추가, cache layer 추가, sharding 등)
  • thread per request 모델이라면 active thread 수가 적절한지 확인
    • 전체 스레드 중 active 스레드의 비율이 높다면, 스레드의 총 개수를 늘려야 함
  • DBCP의 active connection 수가 적절한지 확인
    • DBCP의 active connection 수가 적절하지 않다면, 사용할 백엔드 서버 수를 고려하여 DBCP의 maximumPoolSize를 늘려야 함

성능 측정 지표

  • Throughput: 시간당 처리량 TPS, RPS등으로 불리며 시스템이 얼마나 많은 요청을 처리할 수 있는지를 나타냄
  • Latency: 응답 시간, ART 등으로 불리며 시스템이 얼마나 빠르게 응답하는지를 나타냄 (ART + Network Latency)
  • TPS (Transaction Per Second) : 초당 처리할 수 있는 트랜잭션 수
  • RPS (Request Per Second) : 초당 처리할 수 있는 요청 수
  • ART (Average Response Time) : 평균 응답 시간 (시스템이 요청을 받은 시점부터 응답을 보낼 때까지의 시간)

관련 글

왜 함께 보면 좋은가
DB 앞단 캐싱 전략 — RedisDB 부하를 더는 또 다른 앞단
PostgreSQL 모니터링 — pg_stat·슬로우 쿼리커넥션 상태를 실제로 보는 법

참고 & 출처

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.