본문 바로가기
Q&A해결2026년 8월 9일1분 읽기

요청이 좀 몰리면 "timeout exceeded when trying to acquire a connection"이 나며 API가 멈춥니다

p
pool_sungho
조회 859댓글 4

node-postgres(pg)로 Express API 짰는데요, 평소엔 멀쩡하다가 트래픽이 조금만 몰리면 이 에러가 나면서 그 뒤 요청들이 전부 먹통이 됩니다.

error: timeout exceeded when trying to acquire a connection
    at .../node_modules/pg-pool/index.js:...

pool 사이즈(max)를 10에서 30으로 올려봤는데 그냥 터지는 시점만 조금 늦춰질 뿐 결국 똑같이 멈춰요. 재시작하면 잠깐 괜찮아지고요. DB 서버 자체는 CPU도 커넥션 수도 여유가 있습니다. 코드에서 뭘 잘못하고 있는 걸까요?

댓글 4

김김영삼2개월 전
재시작하면 잠깐 괜찮다가 시간이 지나면 다시 터지는 패턴, 그리고 pool을 키워도 터지는 시점만 미뤄지는 것. 전형적인 커넥션 누수예요. pool.connect()로 클라이언트를 빌려온 다음 어떤 경로에서 client.release()를 안 해서, 반납 안 된 커넥션이 하나씩 쌓이다가 풀이 완전히 고갈되면 그때부터 모두가 acquire 타임아웃을 맞는 겁니다. DB가 멀쩡한 건 원인이 DB가 아니라 앱이 커넥션을 붙잡고 안 놓기 때문이에요.

제일 흔한 범인은 에러 경로에서 release가 빠진 겁니다.

const client = await pool.connect()
const r = await client.query(...) // 여기서 throw나면
client.release() // 이 줄을 못 지나감 → 누수

이렇게 짜면 쿼리가 실패할 때마다 커넥션이 하나씩 샙니다. 반드시 try/finally로 release를 보장하세요.

const client = await pool.connect()
try {
const r = await client.query(...)
return r.rows
} finally {
client.release()
}

finally에 넣으면 성공이든 에러든 무조건 반납됩니다.

추가로 볼 것들. 트랜잭션 경로는 특히 위험해요. BEGIN 해놓고 중간에 에러 나서 ROLLBACK도 release도 못 하고 빠지는 케이스. 트랜잭션 헬퍼를 하나 만들어 finally에서 release, catch에서 ROLLBACK을 강제하세요. 그리고 단순 쿼리 하나만 할 거면 굳이 pool.connect()로 빌리지 말고 pool.query()를 쓰세요. pool.query는 알아서 빌리고 알아서 반납해서 누수 자체가 안 생깁니다. connect()로 직접 빌리는 건 트랜잭션처럼 한 커넥션을 여러 쿼리에 걸쳐 써야 할 때만요.

범인 찾는 법: pool에 totalCount, idleCount, waitingCount가 있어요. 주기적으로 로그로 찍어보면, 누수가 있으면 totalCount는 max에 붙어 있는데 idleCount가 0이고 waitingCount만 계속 쌓이는 그림이 보입니다. 그러면 누수 확정이에요.
ppool_sungho(guest)2개월 전
try/finally로 다 감쌌더니 안정적이네요. 하나만 더요, 그럼 pool max는 어느 정도로 잡는 게 적당한가요? 무조건 크게 잡는 게 좋은 건 아니라고 들어서요.
김김영삼2개월 전
네, 크게 잡는다고 좋은 게 절대 아니에요. 앱 인스턴스마다 풀을 따로 가지니까, (인스턴스 수 × pool max)가 DB로 가는 실제 커넥션 총량입니다. 인스턴스 8개에 max 30이면 240개가 DB에 붙는 거고, Postgres 기본 max_connections가 100이라 그것만으로 DB가 커넥션 부족으로 터질 수 있어요. Postgres는 커넥션 하나가 프로세스 하나라 많다고 빨라지지도 않고, 코어 수의 두세 배를 넘어가면 오히려 경합으로 느려집니다. 그래서 인스턴스당 풀은 보통 한 자릿수에서 10~20 사이로 잡고, (인스턴스 수 × pool max)가 DB max_connections 안에 들어오게 역산하세요. 커넥션을 더 늘려야 하는 상황이면 풀을 키우지 말고 PgBouncer 같은 커넥션 풀러를 앞에 두는 게 정석입니다. 그리고 누수만 없으면 사실 max 10으로도 웬만한 트래픽은 감당돼요. 방금 누수 잡으신 게 max 올리는 것보다 훨씬 효과 컸을 겁니다.
ppool_sungho(guest)2개월 전
idleCount 0에 waiting만 쌓이는 거 딱 보고 확신했습니다. 커넥션 총량을 역산하는 관점은 생각도 못 했네요. 큰 도움 됐습니다!
Ctrl+Enter로 등록