Spring Batch 멀티스레드 환경에서 HikariCP Connection Leak 문제 해결 방법

🔹 개요
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();
}

✅ 이렇게 하면 각 스레드가 독립적인 트랜잭션을 유지하면서도 커넥션이 안정적으로 관리됨.


🔹 결론

  1. HikariCP Connection Leak 경고는 실제 미반환과 장시간 점유를 구분해 진단해야 함.
  2. 멀티스레드 환경에서 @Transactional을 활용하여 커넥션을 효율적으로 관리해야 함.
  3. HikariCP 설정을 최적화하고, leak-detection-threshold 값을 증가시켜 불필요한 경고를 방지해야 함.
  4. 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 트랜잭션 실전 가이드 글을 함께 참고하세요.

관련 글

답글 남기기