🔹 개요
Spring Batch에서 멀티스레드 환경을 사용한 후 HikariCP의 Connection Leak 경고가 발생하는 문제에 대해 분석하고 해결 방법을 정리합니다.
특히 멀티스레드와 HikariCP의 connection-pool 설정, 트랜잭션 관리 방식, DAO 호출 구조가 Connection Leak에 미치는 영향을 집중적으로 다룹니다.
🔹 HikariCP 설정과 Connection Leak 원인 분석
사용된 HikariCP 설정은 다음과 같습니다.
minimum-idle: 2
maximum-pool-size: 30
keepalive-time: 120000
max-lifetime: 1800000
idle-timeout: 120000
leak-detection-threshold: 10000
connection-timeout: 15000
validation-timeout: 10000
이 설정에서 Connection Leak이 발생하는 주요 원인은 다음과 같습니다.
✅ 1. 멀티스레드 환경에서 DAO 호출 방식에 따른 커넥션 과다 사용
- 기본적으로 1개의 트랜잭션은 1개의 커넥션을 사용하지만, 트랜잭션이 적절히 관리되지 않으면 DAO 호출마다 새로운 커넥션을 가져오는 경우가 발생.
- Spring Batch의
Tasklet에서ExecutorService를 사용하면, 각 스레드에서 별도로 커넥션을 획득할 가능성이 있음.
✅ 2. @Transactional이 적용되지 않아 DAO 호출마다 커넥션을 생성하는 경우
@Transactional이 적용되지 않으면 각 DAO 메서드가 개별적인 커넥션을 획득하고 반환.- 트랜잭션을 유지하지 않으면 스레드 개수(예: 10개)보다 더 많은 커넥션을 사용하여 HikariCP 풀 크기(
maximum-pool-size)를 초과할 수 있음.
✅ 3. Propagation.REQUIRES_NEW 사용으로 예상보다 많은 커넥션 사용
@Transactional(propagation = Propagation.REQUIRES_NEW)을 사용하면, 새로운 트랜잭션을 시작하면서 새로운 커넥션을 가져옴.- 한 개의 트랜잭션 안에서
REQUIRES_NEW를 여러 번 사용하면 각 호출마다 새로운 커넥션이 생성될 수 있음.
✅ 4. leak-detection-threshold 설정이 너무 짧아 정상적인 트랜잭션도 Leak으로 감지
leak-detection-threshold: 10000(10초) 설정은 10초 내에 커넥션이 반환되지 않으면 Leak 경고를 발생.- 트랜잭션이 10초 이상 걸리는 경우에도 불필요한 경고가 발생할 수 있음.
🔹 해결 방법
✅ 1. @Transactional을 활용하여 트랜잭션 단위에서 커넥션 관리
- 트랜잭션 내부에서 여러 DAO 호출을 묶어서 실행하면, 하나의 커넥션만 유지할 수 있음.
@Transactional
public void processOrders() {
orderDAO.updateOrderStatus();
orderDAO.insertOrderLog();
orderDAO.updateStockQuantity();
}
✅ 이렇게 하면 여러 DAO 호출이 하나의 트랜잭션 내에서 실행되므로, 불필요한 커넥션 낭비가 줄어듦.
✅ 2. HikariCP 설정 조정
검증이 필요한 설정 예시:
maximum-pool-size: 30
max-lifetime: 900000 # 15분 (Aurora wait_timeout보다 작게)
idle-timeout: 600000 # 10분
leak-detection-threshold: 30000 # 30초
leak-detection-threshold을 30초 이상으로 설정하여 불필요한 Connection Leak 경고 방지.max-lifetime을 Aurora RDS의wait_timeout보다 작게 설정하여 안정적인 커넥션 관리.
✅ 3. Tasklet 내부에서 멀티스레드 실행 시 트랜잭션 처리
- Spring Batch에서
Tasklet내부에서 직접ExecutorService를 사용하는 대신, Spring의TaskExecutor를 활용하는 구성을 검토하되, 실행 스레드별 트랜잭션 경계를 별도로 확인.
@Bean
public Step parallelStep() {
return stepBuilderFactory.get("parallelStep")
.<InputType, OutputType>chunk(10)
.reader(myItemReader())
.processor(myItemProcessor())
.writer(myItemWriter())
.taskExecutor(taskExecutor()) // ✅ 멀티스레드 처리
.throttleLimit(10) // ✅ 최대 10개 스레드
.build();
}
✅ 이렇게 하면 각 스레드가 독립적인 트랜잭션을 유지하면서도 커넥션이 안정적으로 관리됨.
🔹 결론
- HikariCP Connection Leak 경고는 실제 미반환과 장시간 점유를 구분해 진단해야 함.
- 멀티스레드 환경에서
@Transactional을 활용하여 커넥션을 효율적으로 관리해야 함. - HikariCP 설정을 최적화하고,
leak-detection-threshold값을 증가시켜 불필요한 경고를 방지해야 함. - Spring Batch의 멀티스레드 처리는
TaskExecutor를 활용하여 트랜잭션과 커넥션을 안정적으로 관리하는 것이 좋음.
✅ 설정 후 정상·예외 경로의 커넥션 반환을 다시 검증해야 합니다. 🚀
누수 경고와 실제 미반환을 구분하는 점검
위 설정 숫자는 재현 환경의 예시입니다. leakDetectionThreshold 초과 경고만으로 실제 누수를 확정하지 말고, 작업 완료 후 active 연결이 내려오는지와 pending 증가·스레드 덤프를 함께 비교합니다. 임계값을 올리는 것은 누수의 해결책이 아닙니다.
같은 스레드·같은 DataSource·같은 트랜잭션 매니저 안에서의 재사용과, 다른 스레드 또는 REQUIRES_NEW 호출을 구분합니다. TaskExecutor로 바꾸기만 해서는 부모 스레드의 트랜잭션이 전파되지 않습니다. 본문의 StepBuilderFactory·throttleLimit 예제는 이전 API 형태이므로 Spring Batch 5 이상에서는 사용하는 버전의 StepBuilder와 동시성 제어 방식에 맞춰야 합니다. Spring 트랜잭션 전파 문서.
이어서 확인할 문제 해결 글
MyBatis·slave DataSource 트랜잭션의 커넥션 반환 비교에는 Spring Batch에서 slave DB 커넥션 반환 문제와 MyBatis + Hikari 트랜잭션 실전 가이드 글을 함께 참고하세요.