| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- Visual Studio Code
- 자바
- IntelliJ
- 인텔리제이
- 문자열
- string
- Array
- js
- HashMap
- 스프링부트
- list
- junit5
- html
- ArrayList
- math
- Java
- 테스트자동화
- 배열
- javascript
- 단위테스트
- java테스트
- SpringBoot
- 자바문법
- CSS
- 정규식
- input
- Eclipse
- vscode
- 자바스크립트
- junit
- Today
- Total
어제 오늘 내일
[Spring Boot] "DB 연결이 안 돼서 서버가 멈췄어요..." HikariCP 설정과 데드락(Deadlock) 방지 튜닝 본문
[Spring Boot] "DB 연결이 안 돼서 서버가 멈췄어요..." HikariCP 설정과 데드락(Deadlock) 방지 튜닝
hi.anna 2026. 3. 26. 09:50
서버 개발자가 가장 많이 하는 실수 중 하나가 "DB 커넥션 풀 설정을 기본값(Default)으로 두는 것"입니다.
HikariCP의 기본 최대 연결 수는 10개입니다. 트래픽이 적을 땐 문제없지만, 조금만 몰려도 11번째 사용자는 하염없이 기다리다가 타임아웃 에러를 맞게 됩니다.
그렇다면 무조건 많이 늘리면 될까요? 아니요. 너무 많으면 오히려 느려집니다.
오늘은 HikariCP의 동작 원리와 최적의 풀 사이즈(Pool Size)를 구하는 공식을 알아보겠습니다.
1. 커넥션 풀(Connection Pool)이 뭔가요?
DB 연결(TCP Connection)을 맺는 과정은 비용이 매우 비쌉니다. (3-way handshake 등)
사용자가 올 때마다 연결을 새로 만들면 서버는 금방 지칩니다.
그래서 미리 연결(Connection)을 여러 개 만들어두고(Pool), 필요할 때 빌려주고 다 쓰면 돌려받는 방식을 씁니다. 스프링 부트 2.0부터는 HikariCP가 기본값입니다. (압도적으로 빠르거든요!)
2. 왜 10개로 부족할까요? (데드락의 위험)
단순히 사용자가 많아서 부족한 경우도 있지만, 더 무서운 건 풀 락(Pool Locking)입니다.
💀 데드락 시나리오 (풀 사이즈: 1개 가정)
- 쓰레드 A가 트랜잭션을 시작하고 커넥션을 빌립니다. (풀: 0개 남음)
- 쓰레드 A가 로직을 수행하다가, 내부에서 새로운 트랜잭션(REQUIRES_NEW)을 시작하려고 합니다.
- 새 커넥션이 필요한데, 풀에 남은 게 없습니다. (자기가 쓰고 있으니까!)
- 쓰레드 A는 커넥션이 날 때까지 기다립니다.
- 하지만 커넥션을 반납해야 할 쓰레드 A가 기다리고 있으니 영원히 반납되지 않습니다. -> 서버 멈춤(Hang)
이걸 막으려면 풀 사이즈를 적절하게 늘려야 합니다.
3. 적정 사이즈 구하는 공식 (The Formula)
HikariCP의 제작자와 하이버네이트 전문가들이 추천하는 공식이 있습니다.
- ** (전체 쓰레드 수):** 톰캣의 최대 쓰레드 개수 (기본 200개)
- ** (하나의 작업에서 동시에 필요한 커넥션 수):** 보통은 1개지만, 중첩 트랜잭션을 쓴다면 2개 이상.
💡 실무 꿀팁 (단순 공식)
복잡하다면 이 공식을 기억하세요.
DB CPU 코어 수 * 2 + (디스크 개수)
예를 들어 DB 서버가 4코어라면? 4 * 2 + 1 = 9~10개가 최적입니다.
"어? 생각보다 적은데요?"
맞습니다. 커넥션은 쓰레드(CPU)가 일하는 공간입니다. CPU가 4개뿐인데 커넥션을 100개 만들어봤자, 96개는 놀고 있거나 서로 자리를 뺏느라(Context Switching) 더 느려집니다.
4. 설정 적용하기 ()
이제 이론을 바탕으로 설정을 바꿔봅시다. (4코어 DB 기준 예시)
spring:
datasource:
hikari:
# 1. 최대 커넥션 수 (가장 중요!)
# 공식: Core * 2 + effective_spindle_count
maximum-pool-size: 10
# 2. 최소 유휴 커넥션 (성능을 위해 max와 똑같이 맞추는 것 추천)
minimum-idle: 10
# 3. 커넥션 타임아웃 (기본 30초 -> 10초 추천)
# 10초 동안 못 빌리면 에러 뱉고 빨리 실패 처리(Fail Fast)하는 게 낫습니다.
connection-timeout: 10000
# 4. 커넥션 최대 수명 (기본 30분)
# 너무 길면 DB 쪽에서 끊어버릴 수 있으니 적당히 조절
max-lifetime: 1800000
튜닝 포인트:
minimum-idle= `maximum-pool-size`: 커넥션을 줬다 뺐다 하면서 낭비하지 말고, 그냥 고정(Fixed)으로 만들어두는 게 성능상 유리합니다.connection-timeout: 30초는 사용자가 기다리기에 너무 깁니다. 차라리 빨리 에러를 내고 "잠시 후 다시 시도해주세요"라고 하는 게 UX상 낫습니다.
5. 모니터링은 필수!
설정을 바꿨다면 잘 돌아가는지 봐야겠죠?
지난번에 설치한 Prometheus + Grafana 대시보드를 다시 봅니다.
- Active Connections: 현재 사용 중인 개수
- Idle Connections: 놀고 있는 개수
- Pending Connections: 커넥션을 얻으려고 줄 서서 기다리는 쓰레드 개수 (★이게 0이어야 합니다!)
만약 Pending이 계속 생긴다면? 그때 maximum-pool-size를 조금씩 늘려가며 튜닝하면 됩니다.
마치며
오늘의 결론입니다.
- HikariCP 기본 설정(10개)은 트래픽이 많으면 부족할 수 있다.
- 무조건 늘린다고 좋은 게 아니다. (DB 코어 수 * 2) 공식이 효율적이다.
connection-timeout을 줄여서, 사용자가 무한히 기다리는 상황(Hang)을 막자.
이제 여러분의 서버는 혈액순환이 원활해졌습니다. 더 이상 DB 연결 때문에 멈추는 일은 없을 겁니다.
다음 포스팅에서는 "외부 API가 죽어도 내 서버는 살아야 한다!" 장애 전파를 막는 서킷 브레이커(Circuit Breaker - Resilience4j) 패턴에 대해 알아보겠습니다.
도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊
