DB 커넥션 풀은 어떻게 동작하는가 (HikariCP 내부 구조)

fever·2026년 4월 1일

개발고찰

목록 보기
6/8

백엔드 개발하다 보면 한 번쯤 이런 상황 겪는다.

“DB도 정상이고 CPU도 널널한데, 왜 API만 느리지?”

이럴 때 의외로 많이 걸리는 게 커넥션 풀이다.
나도 처음엔 maxPoolSize만 늘리면 해결되는 줄 알았는데, 구조를 이해하고 나니까 문제 보는 시선이 완전히 달라졌다.

이 글은 설정값 얘기 말고, HikariCP가 실제로 어떻게 동작하는지를 흐름 기준으로 정리한다.


커넥션 풀은 단순 재사용이 아니다

커넥션 풀을 흔히 “미리 만들어두고 재사용”이라고 생각하기 쉬운데, 실제 역할은 그보다 더 중요하다.

커넥션 풀은 결국 동시에 DB에 붙을 수 있는 수를 제한하는 장치다.

요청이 100개 들어와도 커넥션이 10개면, 동시에 처리 가능한 건 10개뿐이다.
나머지는 무조건 기다린다.

그래서 커넥션 풀은 성능을 올리는 도구라기보다
시스템이 감당 가능한 처리량을 강제로 맞추는 장치에 가깝다.

HikariCP 구조를 보면 왜 빠른지 이해된다

HikariCP 내부 구조는 단순한 큐 기반이 아니다.

Application Thread
    ↓
HikariPool
    ↓
ConcurrentBag
    ↓
Connection

여기서 핵심은 ConcurrentBag이다.

일반적인 풀처럼 큐에 넣고 빼는 방식이 아니라,
각 커넥션이 상태를 가지고 있고 스레드가 그 상태를 바꾸면서 가져가는 구조다.

이 방식의 목적은 하나다.

락을 최대한 피하고, 동시에 여러 스레드가 접근해도 병목이 생기지 않게 만드는 것.

왜 굳이 이런 구조를 썼을까

일반적으로는 BlockingQueue를 써서 풀을 만든다.
구현도 쉽고 직관적이다.

근데 트래픽이 늘어나면 문제가 생긴다.

스레드가 많아질수록 lock 경쟁이 심해지고,
대기와 컨텍스트 스위칭이 늘어나면서 전체 성능이 떨어진다.

HikariCP는 이 문제를 피하려고 아래 3가지를 중심으로 설계했다.

  • 가능한 한 lock을 쓰지 않는다
  • 상태 변경은 CAS 방식으로 처리한다
  • 같은 스레드는 같은 커넥션을 다시 쓰도록 유도한다

이 세 가지가 합쳐지면서, 요청이 많아져도 성능이 크게 떨어지지 않는다.

커넥션을 가져오는 실제 흐름

HikariCP에서 커넥션을 얻는 과정은 생각보다 단순하다.

1. ThreadLocal에서 먼저 확인
2. 있으면 그대로 사용
3. 없으면 ConcurrentBag에서 탐색
4. 사용 가능한 커넥션이 있으면 가져옴
5. 없으면 새로 생성 (가능한 경우)
6. 최대치면 대기

여기서 중요한 포인트는 ThreadLocal이다.

같은 스레드는 이전에 사용했던 커넥션을 우선적으로 다시 사용하려고 한다.
이 경우 풀 전체를 탐색하지 않기 때문에 거의 비용이 발생하지 않는다.

결과적으로 반복 요청이 많은 환경에서는
커넥션을 가져오는 비용 자체가 거의 사라진다.

성능을 결정하는 건 커넥션 수가 아니다

많이 하는 실수가 하나 있다.

“느리니까 maxPoolSize를 늘리자”

근데 대부분 이걸로 해결 안 된다.

진짜 중요한 건 커넥션 개수가 아니라
커넥션을 얼마나 오래 잡고 있느냐다.

예를 들어 커넥션이 10개 있고, 각 요청이 1초씩 잡고 있으면
초당 최대 10개밖에 처리 못 한다.

여기서 트랜잭션 안에 외부 API 호출이 들어가면 상황이 더 나빠진다.

DB 작업이 끝났는데도 커넥션을 계속 잡고 있기 때문이다.

이러면 풀을 아무리 늘려도 계속 밀린다.

커넥션 풀이 터지는 순간

모든 커넥션이 사용 중이면 이후 요청은 전부 대기한다.

모든 커넥션 사용 중
→ 요청 대기
→ 응답 지연
→ timeout

이 상태가 되면 특징이 있다.

CPU는 정상이고, DB도 정상인데 API만 느리다.

이럴 때 커넥션 풀을 의심해야 한다.

maxPoolSize는 많다고 좋은 게 아니다

maxPoolSize를 늘리면 무조건 좋아질 것 같지만, 실제로는 그렇지 않다.

DB도 동시에 처리할 수 있는 한계가 있고,
커넥션이 많아질수록 스레드 간 경쟁과 컨텍스트 스위칭이 늘어난다.

결국 일정 수준을 넘으면 오히려 성능이 떨어진다.

중요한 건 단순히 늘리는 게 아니라
DB가 감당 가능한 수준에 맞추는 것이다.

마무리

처음에는 커넥션 풀을 그냥 설정값으로만 봤는데,
구조를 이해하고 나니까 문제를 보는 기준이 완전히 달라졌다.

이제는 API가 느려지면 먼저 본다.

“지금 커넥션을 누가 오래 잡고 있지?”

이 질문이 나오기 시작하면,
단순 튜닝이 아니라 구조를 이해한 상태다.

profile
선명한 삶을 살기 위하여

0개의 댓글