Spring Batch 멀티스레드 처리 중 대기 상태 발생 및 트랜잭션 롤백 해결 방법

Spring Batch 환경에서 멀티스레드 처리 중 특정 스레드가 대기 상태에 빠지고, Pod가 계속 Running 상태가 되는 문제를 해결하는 방법을 정리합니다.

1️⃣ 문제 상황
  • Spring Batch에서 멀티스레드로 데이터를 병렬 처리
  • 특정 스레드(예: 4번)가 무한 대기 상태에 빠짐
  • Pod가 종료되지 않고 Running 상태에서 멈춤
2️⃣ 대기 상태가 발생하는 원인 분석
  • 병렬 실행 중 데드락, 외부 API 응답 대기, DB Lock 등으로 무한 대기 발생 가능
  • 특정 스레드가 예외 발생 후 CountDownLatch.countDown()이 호출되지 않아 무한 대기 상태
  • ExecutorService.submit() 호출 후, 작업이 실행되지 않거나 중단됨
3️⃣ 해결 방법

✅ 스레드 타임아웃 적용

  • CompletableFuture.get(30, TimeUnit.SECONDS)를 사용하여 30초 초과 시 강제 종료

✅ 예외 발생 시 CountDownLatch.countDown() 보장

  • finally 블록을 사용하여 무조건 countDown() 호출

✅ 대기 상태 감지 및 강제 종료 (shutdownNow() 활용)

  • executorService.awaitTermination(60, TimeUnit.SECONDS)을 사용하여 일정 시간 내 종료되지 않으면 강제 종료

✅ 멀티스레드 환경에서 트랜잭션 롤백 적용

  • 개별 트랜잭션을 관리 (@Transactional)
  • Future.get()을 사용하여 멀티스레드 내 예외를 Tasklet으로 전달하여 롤백 가능하도록 처리
4️⃣ 최종 해결 코드 (샘플 코드로 변경)
public static <T> void processDataConcurrently(List<T> dataList, int threadCount, Consumer<List<T>> taskHandler) throws Exception {
if (dataList.isEmpty()) return;

ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
List<Future<?>> futures = new ArrayList<>();

for (List<T> subList : splitDataList(dataList, threadCount)) {
Future<?> future = executorService.submit(() -> taskHandler.accept(subList));
futures.add(future);
}

executorService.shutdown();

for (Future<?> future : futures) {
try {
future.get(30, TimeUnit.SECONDS); // 타임아웃 적용
} catch (ExecutionException | TimeoutException e) {
throw new RuntimeException("Concurrent processing failed", e.getCause()); // 예외를 감지 가능하도록 처리
}
}
}

🚀 이제 개별 스레드에서 발생한 예외를 감지하여 전체 트랜잭션을 롤백할 수 있습니다.

관련 글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다