DB 커넥션 풀의 최적 사이즈 산정 실패

증상: 데이터베이스 응답 지연 및 커넥션 에러

애플리케이션 로그에 “Connection pool exhausted” 또는 “Timeout waiting for connection from pool”과 같은 에러가 빈번하게 기록되고 있습니까? 웹 서버의 평균 응답 시간이 갑자기 증가하거나, 특정 시간대(예: 출근 시간, 점심 시간)에 서비스 지연이 두드러지게 발생한다면 커넥션 풀 설정이 현실적인 부하를 수용하지 못하고 있을 가능성이 매우 높습니다. 단순히 풀 사이즈를 무작정 늘리는 것은 서버 자원 고갈로 이어질 수 있는 위험한 접근법입니다.

서버 클러스터에 과부하 경고가 발생하여 빨간색 연결선이 끊어지고 데이터베이스 아이콘이 경고 신호와 함께 대기 패킷을 보여주는 시스템 장애 상황을 묘사한 이미지입니다.

원인 분석: 잘못된 풀 사이즈 산정의 근본 요인

커넥션 풀 최적화 실패는 단일 원인이 아닌 여러 요소가 복합적으로 작용한 결과입니다. 가장 흔한 실수는 “동시 사용자 수 = 필요한 커넥션 수”라는 오해에서 비롯됩니다. 실제로 필요한 커넥션 수는 동시 실행 가능한 활성 트랜잭션 수에 더 가깝습니다. 사용자가 요청을 보내고 데이터베이스에서 응답을 받을 때까지의 시간(지연 시간, Latency)과 애플리케이션의 비즈니스 로직 처리 시간이 풀 내 커넥션 점유 시간을 결정짓습니다. 따라서 높은 지연 시간 환경에서는 적은 수의 사용자도 많은 커넥션을 빠르게 소진시킬 수 있습니다.

해결 방법 1: 현재 시스템 상태 기반 현실적 진단 수행

감정이 아닌 데이터로 접근해야 합니다. 우선 현재 시스템이 어떻게 동작하는지 측정하는 것이 모든 시작점입니다.

  1. 데이터베이스 세션 모니터링: DBMS(예: MySQL, PostgreSQL) 관리 도구에 접속해 현재 활성(Active) 세션 수와 대기(Idle) 세션 수, 세션당 평균 실행 시간을 확인합니다. SHOW PROCESSLIST;(MySQL) 또는 pg_stat_activity(PostgreSQL) 뷰를 활용하십시오.
  2. 애플리케이션 연결 풀 지표 수집: 사용 중인 애플리케이션 프레임워크(Spring Boot, Tomcat, HikariCP 등)의 관리 엔드포인트 또는 JMX를 통해 풀의 주요 지표를 확인합니다. 핵심 지표는 다음과 같습니다.
    • 활성 커넥션 수(Active Connections)
    • 대기 커넥션 수(Idle Connections)
    • 커넥션 대기 시간(Connection Wait Time)
    • 커넥션 타임아웃 발생 횟수
  3. 피크 시간대 트래픽 패턴 분석: APM(Application Performance Monitoring) 도구나 서버 액세스 로그를 분석하여 시간당 요청 수(RPS), 평균/최대 응답 시간, 그리고 데이터베이스 쿼리가 이 중 차지하는 비율을 파악합니다.

초기 풀 사이즈 계산 공식 적용

측정된 데이터를 아래 공식에 대입하여 이론적인 시작점을 찾습니다.

필요 예상 커넥션 수 ≈ (피크 시간대 초당 요청 수) × (평균 데이터베이스 응답 시간(초))

실제로, 피크 시간대 RPS가 100이고, 평균 DB 응답 시간이 0.05초(50ms)라면, 약 5개의 커넥션으로 이론상 처리가 가능합니다. 그러나 이는 이상적인 환경을 가정한 것이므로, 안전 마진을 고려해야 합니다.

해결 방법 2: 부하 테스트를 통한 정밀 튜닝 및 검증

계산만으로는 부족합니다. 실제 부하를 시뮬레이션하여 시스템의 한계점과 최적의 지점을 찾아야 합니다. JMeter, Gatling 등의 도구를 사용합니다.

  1. 기준 설정: 현재 프로덕션 환경의 풀 설정으로 부하 테스트를 수행하여 기준 성능(처리량, 응답 시간, 에러율)을 기록합니다.
  2. 변수 변경 및 관찰: 커넥션 풀의 최대 사이즈(maxActive 또는 maximumPoolSize)를 단계적으로(예: 10, 20, 50, 100) 증가시키며 각 단계별 테스트를 수행합니다. 각 테스트 시나리오마다 다음을 주시하십시오.
    • 애플리케이션 응답 시간이 개선되는가?
    • 데이터베이스 서버의 CPU와 메모리 사용률은 어떻게 변하는가? (70-80%를 초과하면 위험 신호)
    • 커넥션 대기 시간이 0에 수렴하는가?
  3. 한계점 식별: 풀 사이즈를 증가시켜도 응답 시간이 더 이상 개선되지 않거나, 오히려 데이터베이스 서버의 리소스 사용률이 치명적으로 높아지는 지점이 최대 효과적인 사이즈의 상한선입니다. 이 지점 바로 전의 값이 최적값 후보입니다.

해결 방법 3: 고급 설정을 통한 풀 효율 극대화

단순한 크기 조절 외에 풀의 동작 방식을 세밀하게 제어하여 같은 자원으로 더 높은 처리량을 얻을 수 있습니다.

  1. 커넥션 유효성 검사(Validation Query) 설정: 네트워크 불안정이나 DB 재시작으로 인해 끊어진 커넥션을 풀이 인식하지 못하면 에러로 이어집니다. 풀에서 커넥션을 애플리케이션에 제공하기 전 간단한 쿼리(예: MySQL의 SELECT 1)로 유효성을 검사하도록 validationQuery를 설정합니다. 주기적으로 검사하는 testWhileIdle 옵션도 함께 활성화하는 것이 좋습니다.
  2. 대기 시간(Wait Timeout) 조정: 모든 커넥션이 사용 중일 때 새로운 요청이 기다리는 최대 시간을 설정합니다. 너무 길면 요청이 불필요하게 큐에 머무르고, 너무 짧으면 사용자는 즉시 에러를 보게 됩니다. 일반적으로 평균 트랜잭션 시간의 2~3배를 시작점으로 삼아 테스트하십시오.
  3. 최소 유휴 커넥션(Min Idle) 관리: 풀은 최소한 이 수치만큼의 커넥션을 유휴 상태로 유지하며 준비시킵니다. 트래픽이 갑자기 증가할 때 커넥션을 새로 생성하는 오버헤드를 줄여줍니다. 피크 커넥션 수의 20~30% 수준으로 설정하는 것이 일반적입니다.
  4. 커넥션 수명 제한: 장시간 유휴 상태로 있던 커넥션에는 메모리 누수나 세션 상태 이상이 발생할 수 있습니다. maxLifetime 또는 removeAbandonedTimeout 설정을 통해 일정 시간(예: 30분)이 지난 커넥션은 풀에서 제거하고 새 것으로 교체하도록 합니다.

주의사항: 풀 튜닝 시 반드시 지켜야 할 원칙

커넥션 풀 설정은 애플리케이션과 데이터베이스 전체 시스템의 허리와 같습니다. 잘못된 설정은 전체 서비스를 마비시킬 수 있습니다.

데이터베이스 서버의 최대 동시 연결 수 제한을 먼저 확인하십시오. 애플리케이션 풀의 최대 사이즈가 DB 서버의 최대 연결 수를 초과하도록 설정하는 것은 의미가 없으며, 오히려 DB 서버를 과부하 상태로 만들어 다른 서비스까지 영향을 미칠 수 있습니다. 애플리케이션 서버 대수가 많을 경우, (애플리케이션 서버 대수 × 풀 최대 사이즈)의 총합이 DB 서버 제한을 넘지 않도록 분배 계산이 필수입니다.

더불어, 모든 설정 변경은 스테이징(Staging) 환경에서 충분한 부하 테스트를 거친 후에야 프로덕션에 적용해야 합니다. 변경 시에는 한 번에 하나의 변수만 조정하여 그 영향도를 명확히 관찰하는 것이 좋습니다.

전문가 팁: 장기적 관점에서의 근본 해결

커넥션 풀 사이즈를 늘리는 것은 일종의 ‘증상 완화’에 불과할 수 있습니다. 지속적으로 풀 부족이 발생한다면, 애플리케이션 코드와 데이터베이스 쿼리를 반드시 병행 최적화하십시오. 이러한 n+1 쿼리 문제 해결, 비효율적인 조인 최적화, 적절한 인덱스 추가는 단일 트랜잭션의 실행 시간을 획기적으로 줄여, 결과적으로 필요한 커넥션 수 자체를 감소시킵니다. 또한, 읽기/쓰기 작업을 분리하여 슬레이브(Slave) 데이터베이스로 읽기 부하를 분산시키는 아키텍처 개선을 고려해 보십시오, 가장 비싼 커넥션은 사용하지 않고 대기 중인 커넥션이 아니라, 느린 쿼리를 실행하며 점유된 커넥션입니다.