DB Connection Pool — HikariCP를 앱과 DB 사이에서 사이징하기
Connection Pool을 요청 대기열과 DB 동시 처리량 사이의 경계로 보고 maximumPoolSize, connectionTimeout, maxLifetime을 DB 제한과 부하 테스트에 맞춰 조정하는 흐름을 정리한다.
Connection Pool을 단순히 “DB Connection을 재사용하는 Cache”로 보면 maximumPoolSize를 크게 잡고 싶어진다. 하지만 Pool의 실제 위치는 Application의 동시 요청과 DB가 실제로 처리할 수 있는 동시 작업 사이다.
1
2
3
4
5
6
7
8
9
Application Request
↓
Application Thread
↓
Connection Pool
↓
DB Session
↓
CPU / I/O / Lock
Pool이 너무 작으면 Application에서 Connection을 기다리는 요청이 늘고, 너무 크면 그 대기를 DB 내부로 밀어 넣어 CPU 경쟁·I/O 경쟁·Lock 경합을 키울 수 있다. 따라서 목표는 Connection 수를 최대화하는 것이 아니라 병목을 통제 가능한 위치에 두는 것이다.
이 글은 HikariCP를 기준으로 Pool Size와 Lifetime을 어떻게 판단할지 정리한다.
먼저 두 종류의 문제를 구분한다
1
2
3
4
5
6
7
8
9
Connection을 못 얻는다
├─ Pool이 포화됐는가?
├─ Query가 오래 Connection을 점유하는가?
└─ Application 동시성이 과도한가?
Connection이 자주 끊긴다
├─ DB idle timeout인가?
├─ Proxy / Network timeout인가?
└─ Pool lifetime이 외부 timeout보다 긴가?
Pool Size 문제와 Connection Lifetime 문제는 설정 이름이 같은 화면에 있어도 원인이 다르다.
Connection Pool이 필요한 이유
매 요청마다 새 DB Connection을 만들면 TCP 연결, 인증, DB Session 초기화 비용을 반복한다. Pool은 이미 만들어진 Connection을 빌려주고 반환받아 이 비용을 줄인다.
1
2
3
4
5
6
7
요청
↓ borrow
[ Connection Pool ]
↓
Query 실행
↓ return
[ Connection Pool ]
여기서 중요한 것은 Pool이 DB 처리 능력을 늘려주지는 않는다는 점이다. Connection 생성 비용을 줄이고 동시 접근을 제한·재사용하는 계층이다.
HikariCP에서 먼저 볼 네 설정
| 설정 | 답하는 질문 |
|---|---|
maximumPoolSize | 동시에 몇 개의 DB Connection까지 허용할 것인가 |
minimumIdle | 사용하지 않아도 몇 개를 준비해 둘 것인가 |
connectionTimeout | Pool이 포화됐을 때 요청을 얼마나 기다리게 할 것인가 |
maxLifetime | Connection을 최대 얼마 동안 재사용할 것인가 |
maximumPoolSize
Pool의 Active + Idle Connection 총 상한이다.
Application Instance가 여러 개라면 한 Instance의 숫자만 보면 안 된다.
1
2
3
4
5
6
7
DB가 받을 수 있는 Connection 예산
↓
Application Instance 수
↓
Instance별 maximumPoolSize
+
관리·Batch·기타 Client 여유
예를 들어 App Instance 10개가 각각 Pool 30개를 가질 수 있다면 Application만으로 최대 300 Connection을 만들 수 있다. DB의 max_connections는 다른 Client까지 포함하므로 반드시 전체 예산으로 본다.
minimumIdle
Idle 상태로 유지하려는 최소 Connection 수다. HikariCP에서는 별도로 지정하지 않으면 maximumPoolSize와 같은 값으로 동작해 고정 크기에 가까운 Pool이 된다.
Traffic 변동에 따라 Pool을 작게 유지해야 할 특별한 이유가 없다면, minimumIdle을 별도로 미세 조정하기 전에 최대 Pool Size 자체를 적절하게 잡는 것이 우선이다.
connectionTimeout
Pool에 빈 Connection이 없을 때 요청이 기다릴 최대 시간이다.
1
2
3
4
5
6
7
모든 Connection 사용 중
↓
요청 대기
↓
connectionTimeout 안에 반환됨?
├─ yes → Query 실행
└─ no → Connection 획득 실패
따라서 connectionTimeout 오류는 “Pool Size를 늘려라”라는 뜻이 아니다. Query가 비정상적으로 오래 Connection을 잡는지, DB가 느려졌는지, 동시 요청이 갑자기 늘었는지를 함께 본다.
maxLifetime
Connection을 Pool에서 재사용할 최대 수명이다. Connection이 사용 중이라면 즉시 끊지 않고 반환된 뒤 교체한다.
핵심 원칙은 DB·Proxy·Load Balancer 등 외부 계층이 Connection을 강제로 끊기 전에 Pool이 먼저 교체하도록 하는 것이다.
1
2
3
Pool maxLifetime
<
DB / Proxy / Network가 Connection을 끊는 시간
MySQL/MariaDB에서는 wait_timeout이 관련될 수 있지만, 실제 경로에 Proxy나 Cloud Network 장비가 있다면 더 짧은 Timeout이 존재할 수 있다. 특정한 “몇 초 낮게”보다 경로 전체에서 가장 먼저 끊는 계층을 확인하고 여유를 두는 것이 중요하다.
Pool Size는 DB의 병렬 처리량과 함께 본다
Connection을 늘리면 어느 구간까지는 Application 대기가 줄 수 있다. 하지만 DB의 CPU·I/O·Lock이 이미 포화됐다면 더 많은 Connection은 처리량이 아니라 경쟁을 늘린다.
1
2
3
4
5
6
7
8
9
10
11
12
Pool 너무 작음
→ App에서 대기
→ DB 여유가 남을 수 있음
적정 범위
→ DB 자원을 충분히 사용
→ 처리량 증가
Pool 너무 큼
→ DB 내부 동시 작업 증가
→ CPU / I/O / Lock 경쟁
→ Latency 증가
HikariCP 문서에는 (core_count * 2) + effective_spindle_count라는 오래된 출발점이 소개되어 있다. 이 공식은 정답이나 현대 SSD 환경의 보편 규칙이 아니라, 작은 Pool부터 측정하라는 취지를 설명하는 경험칙으로 보는 편이 안전하다.
실제 값은 Workload와 DB 구조에 따라 부하 테스트로 찾는다.
부하 테스트로 적정점을 찾는다
Pool Size를 몇 단계로 바꿔가며 같은 Workload를 반복한다.
1
2
3
4
5
6
7
Pool 5
↓
Pool 10
↓
Pool 20
↓
Pool 40
각 단계에서 최소한 다음을 함께 본다.
Application
- RPS / TPS
- Latency와 Tail Latency(p95, p99)
- Connection 획득 대기 시간
- Active / Idle / Pending Connection
- Request Thread 상태
Database
- CPU
- Disk I/O
- Lock / Wait
- Active Session
- Slow Query
PostgreSQL이라면 PostgreSQL 모니터링 — 증상에서 pg_stat으로 원인 좁히기의 pg_stat_activity와 pg_stat_statements를 같이 볼 수 있다.
판단은 다음과 같이 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
Pool 증가
+
Throughput 증가
+
DB 여유 있음
→ 더 늘릴 여지가 있음
Pool 증가
+
Throughput 거의 동일
+
Latency / DB Wait 증가
→ 이미 병렬 처리 한계를 넘었을 가능성
즉 가장 큰 숫자가 아니라 처리량이 더 이상 의미 있게 늘지 않기 직전의 안정적인 범위를 찾는다.
장애 증상에서 역으로 본다
connectionTimeout이 발생한다
먼저 Pool을 늘리지 않는다.
1
2
3
4
5
6
7
Pending 증가
↓
Active Connection은 무엇을 하나?
↓
Long Query / Lock / DB Slow 확인
↓
DB에 여유가 있으면 Pool 조정 검토
DB에서 Too many connections가 발생한다
Application Instance 전체의 Pool 상한을 합산한다.
1
2
3
Instance 수 × maximumPoolSize
+
Batch / Admin / 기타 Client
DB의 Connection 예산을 넘는다면 Instance별 Pool을 줄이거나 Application 구조와 DB Capacity를 함께 조정한다.
Connection이 간헐적으로 죽는다
Pool Size가 아니라 Lifetime 경로를 본다.
1
2
3
4
5
6
7
Application
↓
HikariCP maxLifetime
↓
Proxy / LB timeout
↓
DB idle timeout
가장 먼저 Connection을 종료하는 계층보다 Pool이 먼저 Connection을 교체하도록 맞춘다.
정리
Connection Pool은 DB 성능을 만들어내는 장치가 아니라 Application의 동시성과 DB의 처리 능력 사이를 조절하는 경계다.
1
2
3
4
5
6
7
8
maximumPoolSize
→ 동시성 상한
connectionTimeout
→ Pool 포화 시 대기 한계
maxLifetime
→ 외부 Connection 수명과의 정합성
따라서 설정 순서는 다음처럼 잡는 편이 좋다.
1
2
3
4
5
6
7
8
9
10
11
DB Connection 예산 확인
↓
작은 Pool에서 시작
↓
부하 테스트
↓
App Pending + DB Wait 함께 관측
↓
Throughput / Latency의 변곡점 확인
↓
Lifetime은 DB·Network Timeout과 별도 정합
Pool Size 문제를 DB Capacity 문제와 분리하지 않는 것이 가장 중요한 원칙이다.