Spring Batch 환경에서 멀티스레드 처리 중 특정 스레드가 대기 상태에 빠지고, Pod가 계속 Running 상태가 되는 문제를 해결하는 방법을 정리합니다.
문제 상황
- Spring Batch에서 멀티스레드로 데이터를 병렬 처리 중
- 특정 스레드(예: 4번)가 무한 대기 상태에 빠짐
- Pod가 종료되지 않고 Running 상태에서 멈춤
대기 상태가 발생하는 원인 분석
- 병렬 실행 중 데드락, 외부 API 응답 대기, DB Lock 등으로 무한 대기 발생 가능
- 특정 스레드가 예외 발생 후
CountDownLatch.countDown()
이 호출되지 않아 무한 대기 상태 ExecutorService.submit()
호출 후, 작업이 실행되지 않거나 중단됨
해결 방법
스레드 타임아웃 적용
CompletableFuture.get(30, TimeUnit.SECONDS)
를 사용하여 30초 초과 시 강제 종료
예외 발생 시
CountDownLatch.countDown()
보장
finally
블록을 사용하여 무조건countDown()
호출
대기 상태 감지 및 강제 종료 (
shutdownNow()
활용)
executorService.awaitTermination(60, TimeUnit.SECONDS)
을 사용하여 일정 시간 내 종료되지 않으면 강제 종료
멀티스레드 환경에서 트랜잭션 롤백 적용
- 개별 트랜잭션을 관리 (
@Transactional
) Future.get()
을 사용하여 멀티스레드 내 예외를Tasklet
으로 전달하여 롤백 가능하도록 처리
최종 해결 코드 (샘플 코드로 변경)
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()); // 예외를 감지 가능하도록 처리
}
}
}
이제 개별 스레드에서 발생한 예외를 감지하여 전체 트랜잭션을 롤백할 수 있습니다.