커넥션 풀을 키웠는데 더 느려졌다
2025-12-01 · 2 min read
$ SELECT count(*) FROM pg_stat_activity
198 / max_connections 100
풀을 키우는 건 대개 반대 방향이다
DB가 느려서 커넥션 풀을 20 → 100으로 올렸다. 더 느려졌다. 직관에 반하는데, 이유는 명확하다.
DB 커넥션 하나는 서버 쪽 프로세스/스레드 하나다. 100개가 동시에 일하려 해도 실제로 병렬로 처리되는 건 코어 수만큼이다. 나머지는 컨텍스트 스위칭과 락 경합만 늘린다. 대기줄을 앱에서 DB로 옮긴 것뿐인데, DB 안에서의 대기가 더 비싸다.
시작점 공식
널리 쓰이는 출발점은 이거다.
connections = (코어 수 × 2) + 유효 디스크 수
4코어 + SSD 1개면 9. 그래서 웹서버 한 대당 10 근처가 흔한 정답이 된다. "동시 요청이 200개니까 풀도 200"은 잘못된 계산이다. 요청 200개가 커넥션 10개를 돌려 쓰는 게 정상 동작이다.
이건 공식이지 진리가 아니다. 실제로는 부하를 걸고 p99 레이턴시가 꺾이는 지점을 찾아야 한다. 처리량은 풀을 키우면 잠깐 오르다 어느 순간 레이턴시만 오르고 처리량은 안 오른다. 그 직전이 답이다.
전체 커넥션 수를 계산했는가
여기서 실제로 서버를 죽였다. 앱 서버는 한 대가 아니다.
서버 8대 × 풀 20 = 160
+ 배치 워커 4개 × 10 = 40
+ 로컬 개발 / 마이그레이션 툴 / 관리 콘솔
= DB max_connections 초과
풀 사이즈를 정할 때는 인스턴스 수를 곱해서 DB 한도와 비교해야 한다. 오토스케일링이 붙어 있으면 최대 인스턴스 수 기준으로 계산한다. 스케일 아웃이 DB 커넥션 고갈로 이어지는 게 이 실수의 전형적인 결말이다.
SHOW max_connections; -- Postgres 한도
SELECT count(*) FROM pg_stat_activity; -- 지금 몇 개 쓰는지타임아웃 세 개를 구분한다
| 설정 | 의미 | 안 걸면 |
|---|---|---|
connectionTimeout | 풀에서 커넥션 빌리는 대기 한도 | 풀 고갈 시 요청이 무한 대기 |
idleTimeout | 안 쓰는 커넥션 반납까지 | 새벽 트래픽에도 커넥션 점유 |
maxLifetime | 커넥션 최대 수명 | DB/프록시가 먼저 끊은 좀비 커넥션을 씀 |
셋 중 제일 자주 사고 나는 건 connectionTimeout이다.
안 걸어두면 DB가 느려졌을 때 요청 스레드가 전부 대기에 잠겨 서버가 멎는다.
2~3초로 짧게 걸고 빠르게 503을 내는 게, 전체가 멈추는 것보다 낫다.
maxLifetime은 DB의 wait_timeout보다 짧게 잡는다.
길면 이미 서버가 끊은 커넥션을 풀이 유효하다고 믿고 내주고, 그게 간헐적
"connection reset" 에러로 나타난다. 재현이 안 돼서 찾는 데 오래 걸린다.
서버리스는 이 계산이 안 통한다
Vercel Functions 같은 환경에서는 인스턴스가 요청량에 따라 수백 개로 늘어난다. 인스턴스마다 풀을 들면 커넥션이 폭발한다. 여기서 필요한 건 풀 튜닝이 아니라 바깥의 커넥션 풀러다.
- Postgres: PgBouncer, Supabase Pooler, Neon 등의 pooled 엔드포인트
- 풀러 모드가
transaction이면 prepared statement·세션 변수가 제한된다. ORM 설정에서 이걸 꺼줘야 하는 경우가 많다
인스턴스 안의 풀 사이즈는 오히려 1~2로 줄인다. 인스턴스가 늘어나는 게 동시성 확보 수단이라서, 안에서까지 늘리면 곱연산이 된다.
확인할 것 하나
느린 원인이 정말 커넥션 부족인지부터 본다.
-- 대기 중인 세션이 뭘 기다리는지
SELECT wait_event_type, wait_event, count(*)
FROM pg_stat_activity WHERE state = 'active'
GROUP BY 1, 2 ORDER BY 3 DESC;Lock이 많으면 풀 문제가 아니라 트랜잭션이 긴 것이고,
IO가 많으면 인덱스 문제다. 둘 다 풀을 키워서 해결되지 않는다.
오히려 악화된다.
한 줄 요약
풀 사이즈는 코어 수 기준으로 작게, 인스턴스 수를 곱해 DB 한도와 대조하고, 서버리스면 바깥 풀러를 쓴다.