본문 바로가기
Backend2026년 8월 5일5분 읽기

커넥션 풀 사이징 — 크게 잡을수록 느려지는 역설

YS
김영삼
조회 8
커넥션 풀 사이징 — 크게 잡을수록 느려지는 역설

데이터베이스 커넥션 풀(connection pool)은 DB 연결을 매번 새로 열고 닫는 대신, 미리 만들어 둔 연결 몇 개를 재사용하도록 관리하는 장치입니다. 그리고 풀 크기 설정은 이 연결을 몇 개나 둘지 정하는 문제인데, 직관과 정반대로 크게 잡을수록 오히려 느려지는 경우가 많습니다. 이 글은 그 이유와 적정 크기를 잡는 법에 관한 이야기입니다.

커넥션 풀 고갈은 백엔드에서 가장 자주 만나는 장애 중 하나입니다. 증상은 늘 비슷해요. API가 갑자기 전부 느려지고, 로그엔 "connection timeout" 또는 "pool exhausted"가 찍힙니다. DB는 멀쩡한데 말이죠.

왜 매번 연결하면 안 되나

DB 연결을 새로 여는 건 생각보다 비쌉니다. TCP 핸드셰이크, 인증, 세션 초기화까지 왕복이 여러 번 오갑니다. 요청마다 이걸 반복하면 지연이 쌓이고, 특히 PostgreSQL처럼 연결 하나가 OS 프로세스인 경우 서버 자원도 크게 먹습니다. 그래서 연결을 미리 열어두고 돌려쓰는 것이죠. 요청은 풀에서 하나 빌려 쓰고, 끝나면 반납합니다.

고갈은 이렇게 일어난다

풀에 연결이 10개 있는데 동시에 11번째 요청이 오면, 그 요청은 누군가 반납할 때까지 대기합니다. 대기가 타임아웃을 넘기면 에러입니다. 문제는 이게 연쇄적으로 번진다는 점이에요.

  • 느린 쿼리 하나가 연결을 오래 붙잡음 → 반납이 늦어짐 → 대기 줄이 길어짐 → 전부 타임아웃.
  • 트랜잭션 안에서 외부 API를 호출 → 그 응답을 기다리는 내내 연결을 쥐고 있음 → 풀이 순식간에 마름.
  • 연결을 빌리고 반납을 깜빡한 코드(leak) → 풀이 서서히 줄다가 결국 0.

세 번째, 커넥션 누수는 정말 골치 아픕니다. 평소엔 멀쩡하다가 며칠에 한 번씩 풀이 마릅니다. try/finally나 using으로 반드시 반납하도록 짜는 게 기본입니다.

크게 잡을수록 느려지는 역설

"느리면 풀을 키우면 되지"가 첫 반응입니다. 나도 그랬어요. 그런데 커넥션을 100개, 200개로 늘렸더니 오히려 처리량이 떨어졌습니다. 이유는 DB 쪽에 있습니다. CPU 코어와 디스크는 한정돼 있는데, 동시에 실행되는 쿼리가 너무 많으면 컨텍스트 스위칭·락 경합·디스크 경쟁으로 전체가 느려집니다. 식당 주방에 요리사 100명을 욱여넣으면 서로 부딪혀 더 느려지는 것과 같아요.

HikariCP 문서가 인용하는 유명한 공식이 있습니다. 만능은 아니지만 출발점으로 훌륭합니다.

연결 수 = ((코어 수 × 2) + 유효 스핀들 수)
예) 8코어, SSD(스핀들 개념 희박)면 대략
연결 수 ≈ 8 × 2 + 1 = 17개 정도부터 시작

즉 수백 개가 아니라 수십 개 이하가 정상입니다. 처음 이 숫자를 보면 너무 작아 보이지만, 짧은 쿼리를 빠르게 돌려 반납하면 적은 연결로도 엄청난 처리량이 나옵니다.

사이징 체크리스트

확인 항목왜 중요한가
앱 인스턴스 수 × 풀 크기이 합이 DB의 max_connections를 넘으면 안 됨
서버리스/오토스케일인스턴스가 폭증하면 연결도 폭증 → 프록시 필요
연결 획득 타임아웃무한 대기 금지, 빠르게 실패시켜야 함
유휴 연결 수명방화벽·DB가 끊은 좀비 연결 정리

특히 서버리스가 함정입니다. 함수 인스턴스가 200개로 튀면 각자 연결을 열어 DB의 max_connections를 순식간에 초과합니다. 이럴 땐 PgBouncer 같은 커넥션 프록시를 앞에 둬 실제 DB 연결 수를 통제하세요.

자주 묻는 질문

풀 크기는 얼마부터 시작하면 되나요?

CPU 코어 수의 2~4배 정도에서 시작해 부하 테스트로 조정하세요. 대부분의 웹 서비스는 인스턴스당 10~20개면 충분합니다. 중요한 건 절대값이 아니라 "전체 앱 인스턴스가 여는 연결의 합이 DB 한도 안에 드는가"입니다.

연결이 부족한지 어떻게 아나요?

연결 획득 대기 시간(acquire wait)과 풀의 활성/유휴 연결 수를 메트릭으로 노출하세요. 대기 시간이 0보다 유의미하게 크면 풀이 부족하거나 쿼리가 느린 겁니다. 후자라면 풀을 키우는 대신 쿼리를 고쳐야 합니다.

트랜잭션 안에서 외부 API를 호출해도 되나요?

피하세요. DB 트랜잭션은 연결을 점유합니다. 그 안에서 느린 외부 호출을 기다리면 연결을 오래 붙잡아 풀을 말립니다. 외부 호출은 트랜잭션 밖에서 하고, 꼭 필요하면 트랜잭션을 짧게 쪼개세요.

PgBouncer 같은 프록시는 언제 쓰나요?

앱 인스턴스가 많거나(마이크로서비스, 서버리스) 오토스케일로 연결 수가 예측 불가일 때 유용합니다. 프록시가 실제 DB 연결을 소수로 모아 재사용하므로, 앱은 많아도 DB가 보는 연결은 적게 유지됩니다. 트랜잭션 풀링 모드에선 prepared statement 등 몇 가지 제약이 있으니 확인하고 쓰세요.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록