[SpringBatch] 재시도

유기훈·2025년 6월 22일

스프링 배치의 재시도

스프링 배치(Spring Batch)는 데이터 처리 중 예외가 발생했을 때 자동으로 재시도하거나 실패를 건너뛰는 로직을 설정할 수 있는 강력한 오류 처리 기능을 제공합니다. 이 중에서도 재시도(Retry)는 특정 작업이 실패했을 때 일정 횟수만큼 다시 시도해보는 기능입니다.

왜 재시도가 필요한가?

배치 시스템에서 외부 시스템 연동(API, DB, 파일 I/O 등)이나 비즈니스 로직에서 일시적인 오류가 발생할 수 있습니다.
예를 들어:

  • 네트워크 타임아웃
  • 일시적인 DB deadlock
  • 간헐적으로 실패하는 API 호출
    이런 문제는 다시 시도하면 해결되는 경우가 많기 때문에, 에러를 곧바로 실패 처리하지 않고 재시도를 통해 복구를 시도하는 것이 안정적인 배치 운영의 핵심입니다.

재시도가 적용되는 위치

스프링 배치에서 재시도는 주로 아래 위치에 설정할 수 있습니다:

  • ItemProcessor
  • ItemWriter
  • ItemReader는 재시도 적용 X

재시도 설정 예시

Step 설정에서 faultTolerant()를 통해 재시도 및 건너뛰기 설정이 가능합니다.

@Bean
public Step retryStep() {
    return stepBuilderFactory.get("retryStep")
        .<InputType, OutputType>chunk(10)
        .reader(itemReader())
        .processor(itemProcessor())
        .writer(itemWriter())
        .faultTolerant()
        .retry(MyTemporaryException.class)
        .retryLimit(3)
        .build();
}
  • retry(): 어떤 예외에 대해 재시도를 수행할지 지정
  • retryLimit(): 최대 재시도 횟수 설정

RetryTemplate

Spring Batch는 내부적으로 재시도 기능을 구현할 때 Spring Retry를 사용하며, 그 중심에 있는 핵심 컴포넌트가 바로 RetryTemplate입니다.

RetryTemplate은 재시도 로직을 캡슐화한 템플릿으로, 실패 시 조건을 기반으로 재시도를 수행하고, 최대 횟수 초과 시 최종 예외를 던지는 방식으로 동작합니다.

RetryTemplate 구성 요소

  1. RetryPolicy: 재시도할 수 있는지 여부를 결정합니다. 예외의 종류, 최대 재시도 횟수 등을 기준으로 판단합니다.
  2. BackOffPolicy: 재시도 사이의 딜레이(대기 시간)을 설정합니다. 빠르게 재시도하는 것이 부담으러운 작업에 유용합니다.
  3. RetryContext: 현재 재시도 상태(재시도 횟수, 마지막 예외 등)를 저장하는 객체입니다.
  4. RecoveryCallback(선택): 모든 재시도 실패 후 수행할 대체 처리 로직을 정의합니다.

예시 코드

RetryTemplate retryTemplate = new RetryTemplate();

// 정책 설정
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(3));
retryTemplate.setBackOffPolicy(new FixedBackOffPolicy());

// 실행
String result = retryTemplate.execute(
    context -> {
        // 재시도 대상 로직
        return someUnstableRemoteCall();
    },
    context -> {
        // 재시도 실패 시 대체 로직
        return "fallback-value";
    }
);

ItemProcessor의 재시도

Spring Batch에서 ItemProcessor는 read로부터 읽어온 데이터를 가공/변환하는 중간 단계입니다.
이 가공 과정에서 예외가 발생할 수 있고, 이를 재시도(Retry)할 수 있도록 설정할 수 있습니다.

재시도가 필요한 예시 상황

  • 외부 API를 호출해 부가 데이터를 가져와야 하는 경우
  • 변환 로직 중 데이터에 따라 특정 오류가 반복적으로 발생할 수 있는 경우
  • 제3자 시스템 호출이 간헐적으로 실패하는 경우

재시도 설정 방법
StepBuilder에서 .faultTolerant()와 .retry(), .retryLimit()을 사용하여 재시도 정책을 지정할 수 있습니다.

@Bean
public Step processorRetryStep() {
    return stepBuilderFactory.get("processorRetryStep")
        .<String, String>chunk(10)
        .reader(itemReader())
        .processor(itemProcessor())
        .writer(itemWriter())
        .faultTolerant()
        .retry(ApiTemporaryException.class)
        .retryLimit(3)
        .build();
}
  • ApiTemporaryException: 처리 중 발생하는 커스텀 예외
  • retryLimit(3): 최대 3번까지 재시도

주의할 점

  • ItemProcessor에서 예외가 발생하면 해당 아이템만 재시도됩니다. (청크 단위 X)
  • 동일 예외에 대해 .retry(...) 지정이 없으면 즉시 실패합니다.
  • 여러 예외 지정 가능

ItemWriter의 재시도 – 설명, 특징, 예시 코드

ItemWriter는 Spring Batch의 Step 내에서 데이터를 외부 시스템(파일, DB, API 등)에 기록하는 역할을 합니다.
이 과정에서 네트워크 오류, DB deadlock, 일시적인 실패 등으로 인해 예외가 발생할 수 있는데, 이런 경우 재시도 기능이 매우 유용합니다.

재시도가 필요한 상황

  • DB 저장 시 Deadlock 또는 Lock Timeout 발생
  • 외부 시스템(API, MQ 등) 연동 중 일시적인 장애
  • 파일 시스템에 일시적 접근 실패

재시도 설정 방법

@Bean
public Step writerRetryStep() {
    return stepBuilderFactory.get("writerRetryStep")
        .<String, String>chunk(10)
        .reader(itemReader())
        .processor(itemProcessor())
        .writer(itemWriter())
        .faultTolerant()
        .retry(DatabaseTemporaryException.class)
        .retryLimit(3)
        .build();
}

특징

  • 재시도 단위: Chunk 단위
  • 최대 재시도 횟수: 설정한 retryLimit 까지만 재시도
  • 실패 시 동작: SkipPolicy가 없으면 Step 실패
  • 상태 저장: 상태 저장 없이 재시도 수행

ItemReader는 재시도 불가능한 이유

이유 1: Reader는 상태가 있는 작업이다
ItemReader는 내부적으로 상태를 가집니다. 예를 들어:

  • Cursor 기반 JDBC Reader는 ResultSet 위치를 유지함
  • FlatFileItemReader는 파일의 현재 위치를 추적함
  • API 기반 Reader는 페이지 정보를 유지함

이유 2: Spring Batch는 Reader의 예외를 Step 실패로 간주
Spring Batch의 기본 동작 방식은:

  • read()에서 예외가 발생하면 → 현재 Step을 즉시 실패 처리
  • faultTolerant()의 retry/skip 정책은 ItemProcessor/Writer에서만 유효

이유 3: 재시도 하려면 ExecutionContext 관리가 복잡해짐
Reader는 보통 상태를 ExecutionContext에 저장하고 재시작 시점에 복원합니다.
하지만 재시도를 위해서는 read 전에 상태를 저장해야 함 → 이는 성능, 구현 복잡도 모두 악화시킴.

profile
개발 블로그

0개의 댓글