Java 아키텍처·Spring 용어 사전
JDBCHikariCP · maximumPoolSize · 커넥션 누수

커넥션 풀

DB 커넥션을 미리 만들어 빌려주고 반납받는 구조. close()는 반납이지 종료가 아니다.

DB 커넥션을 미리 여러 개 맺어 두고 빌려주고 반납받는 구조. Spring Boot의 기본 구현은 HikariCP다.

왜 필요한가

커넥션 하나를 새로 맺는 비용

  • = TCP 3-way handshake + 인증 + 세션 설정
  • ≈ 수~수십 밀리초

요청마다 맺으면 그 자체가 병목이 된다

가장 자주 헷갈리는 것 — close()는 반납이다

Connection con = dataSource.getConnection();
// ...
con.close();      // 물리 연결이 닫히는 게 아니라 '풀로 반납' 된다

반납하지 않으면 커넥션 누수가 되고, 쌓이다 풀이 마르면 새 요청이 멈춘다.

// try-with-resources 로 예외가 나도 반드시 반납되게 한다
try (Connection con = dataSource.getConnection()) { ... }
spring:
  datasource:
    hikari:
      leak-detection-threshold: 2000    # 2초 이상 안 돌려주면 경고 + 스택트레이스
      connection-timeout: 3000          # 기본 30초는 너무 길다
      maximum-pool-size: 20             # 기본 10

풀을 키우면 빨라지지 않는다

풀 100 → 300 으로 늘렸더니 더 느려졌다

이유: 커넥션이 많아지면 DB 쪽에서

  • 동시 세션이 늘어 컨텍스트 스위칭이 증가
  • 락 경합과 메모리 사용이 증가
  • 처리량이 오히려 떨어진다

적정값은 DB 의 코어·디스크 수에 묶이는 작은 수다 흔한 출발점: (코어 수 × 2) + 디스크 수

풀 크기 이전에 쿼리를 빠르게 만드는 것이 먼저다. 느린 쿼리가 커넥션을 오래 쥐면 아무리 키워도 마른다.

고갈의 흔한 원인

  • ① 트랜잭션 안에서 외부 API 호출 — 그동안 커넥션을 쥐고 있다
  • ② OSIV 가 켜진 채 컨트롤러·뷰가 느리다
  • ③ REQUIRES_NEW 중첩 — 요청당 커넥션 2개
  • ④ 슬로우 쿼리
  • ⑤ try-with-resources 없이 직접 JDBC 를 다뤘다

가상 스레드를 켜도 그대로다

스레드가 싸져도 커넥션 풀 크기는 그대로다

  • 풀이 다음 병목으로 드러난다
  • 풀을 키우는 게 아니라 connection-timeout 을 짧게 잡고
    • 자원별 상한을 명시하는 방향이 맞다

면접 함정

  • "풀을 키우면 동시 처리량이 는다" → 어느 지점을 넘으면 DB가 병목이 되어 반대가 된다.
  • "close()하면 연결이 끊긴다" → 풀 환경에서는 반납이다.

지금 풀이 어떤 상태인지 본다

  • Actuator + Micrometer 가 HikariCP 지표를 자동 노출한다

  • hikaricp.connections.active — 사용 중
  • hikaricp.connections.idle — 놀고 있는
  • hikaricp.connections.pending — 커넥션을 기다리는 스레드 수 ← 0이 아니면 경보
  • hikaricp.connections.usage — 빌려 쓴 시간 분포

pending이 지속적으로 0보다 크면 풀이 부족하거나 어딘가 오래 쥐고 있다는 뜻이다. 응답 시간이 나빠지기 전에 잡히는 선행 지표다.

적정 크기를 계산해 본다

동시 처리해야 할 요청 수 × 요청당 커넥션 점유 시간 ÷ 목표 응답 시간

예) 초당 500 요청 · 요청당 커넥션 점유 10ms

  • 500 × 0.01 = 5 (이론상 5개면 충분하다)

실제로는 여유를 두지만, 수백 개가 필요하다는 계산이 나온다면 점유 시간(느린 쿼리 · 트랜잭션 안의 외부 호출)을 먼저 의심한다

누수를 잡는 설정

spring:
  datasource:
    hikari:
      leak-detection-threshold: 2000   # 2초 넘게 안 돌려주면 스택트레이스를 찍는다
      max-lifetime: 1800000            # DB 의 wait_timeout 보다 짧게 (끊긴 커넥션 방지)
      keepalive-time: 300000

max-lifetime을 DB의 유휴 타임아웃보다 짧게 잡지 않으면, DB가 먼저 끊은 커넥션을 풀이 계속 빌려줘 간헐적 오류가 난다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — JDBC — 자바가 DB와 말하는 가장 낮은 층