필자가 담당하고 있는 프로젝트에서 간헐적으로 발생하는 커넥션 풀 고갈 문제를 체계적으로 분석하고 해결한 경험을 공유합니다.
어느 날 모니터링 시스템에서 다음과 같은 에러가 간헐적으로 발생하기 시작했다.
HikariPool-1 - Connection is not available, request timed out after 30012ms
(total=15, active=15, idle=0, waiting=0)
total=15, active=15, idle=0 → 모든 커넥션이 사용 중인 상태spring:
datasource:
hikari:
connection-timeout: 30000
maximum-pool-size: 15
minimum-idle: 5
idle-timeout: 300000
max-lifetime: 600000
가설 1: 커넥션 리크 (Connection Leak)
가설 2: 장기 실행 쿼리
가설 3: DB 서버 성능 이슈
우선순위가 가장 높은 커넥션 리크부터 조사하기로 결정했다.
커넥션 리크 감지 설정 추가:
spring:
datasource:
hikari:
leak-detection-threshold: 30000 # 30초 후 리크로 판단
체계적 코드 리뷰 계획:
@Transactional 없이 직접 커넥션을 다루는 코드 점검try-with-resources 패턴을 사용하지 않은 JDBC 코드 확인finally 블록에서 커넥션을 제대로 닫지 않는 코드 확인리크 감지 설정을 적용한 날, 원인을 찾았다.
2025-06-17 09:34:41 [HikariPool-1 housekeeper] WARN c.zaxxer.hikari.pool.ProxyLeakTask
- Connection leak detection triggered for com.mysql.cj.jdbc.ConnectionImpl@c67a89
on thread http-nio-8080-exec-1
// ❌ 문제: EntityManager 생성 후 close() 누락
public void updateDocInfo(String templateId, String docGroupName,
String title, String guide) {
EntityManager em = emf.createEntityManager(); // 커넥션 획득
em.getTransaction().begin();
Template temp = em.find(Template.class, templateId);
temp.setDocGroupName(docGroupName);
temp.setTitle(title);
temp.setGuide(guide);
em.getTransaction().commit();
// ❌ em.close() 누락! → 커넥션이 HikariCP로 반환되지 않음
}
문제 분석:
EntityManager를 직접 생성했지만 close()를 호출하지 않음Spring의 트랜잭션 관리 기능을 활용하여 EntityManager 생명주기를 Spring이 자동으로 관리하도록 변경
// ✅ 해결: Spring이 EntityManager 생명주기 자동 관리
@Transactional
public void updateDocInfo(String templateId, String docGroupName,
String title, String guide) {
Template template = selectFrom(QTemplate.template)
.where(QTemplate.template.templateId.eq(Long.valueOf(templateId)))
.fetchOne();
if (template == null) {
throw new CustomApiException(CustomErrorCode.DATA_IS_NULL,
"템플릿을 찾을 수 없습니다. with id: " + templateId);
}
template.setDocGroupName(docGroupName);
template.setTitle(title);
template.setGuide(guide);
// ✅ Spring이 자동으로 커넥션 반환 처리
}
# 해결 후 정상적인 커넥션 풀 상태
HikariCP Metrics:
- Active connections: 2-5 (정상 범위)
- Idle connections: 5-10 (정상 범위)
- Connection timeout: 0건 (문제 해결!)
@Transactional의 EntityManager 자동 관리 기능을 몰랐음1. Spring vs 순수 JPA 환경의 차이
| 환경 | EntityManager 관리 방법 |
|---|---|
| Spring 환경 | @Transactional 사용하여 Spring이 자동 관리 |
| 순수 JPA 환경 | try-with-resources 또는 finally에서 명시적 close() |
2. 체계적인 문제 해결 접근법의 중요성
3. 예방 조치의 필요성
# 운영 환경에 영구 적용한 모니터링 설정
spring:
datasource:
hikari:
leak-detection-threshold: 30000
logging:
level:
com.zaxxer.hikari.pool.ProxyLeakTask: WARN
코드 리뷰 체크리스트에 추가:
이번 문제 해결 과정에서 학습한 기술들의 연관관계:
Application (@Transactional)
↓
Spring Transaction Manager
↓
JPA EntityManager
↓
HikariCP Connection Pool
↓
MySQL Database
이번 경험을 통해 단순히 기능을 구현하는 것을 넘어서, 시스템의 안정성과 성능을 고려한 개발의 중요성을 깨달았다.
특히 체계적인 문제 분석과 모니터링의 중요성, 그리고 Spring 생태계에 대한 깊은 이해가 얼마나 중요한지 실감했다.
앞으로도 이런 실무 경험을 통해 더욱 견고한 시스템을 구축하는 개발자로 성장해야겠다.