Spring Framework/Spring

[Spring Batch] skip / retry

덴마크초코우유 2025. 2. 13. 20:54
반응형
  • Spring Batch에서 일부 오류 데이터 때문에 전체 배치가 실패하는 문제를 해결하는 방법
  • 가령 10만 건 데이터 처리 중 1건의 오류로 전체 작업이 중단되거나, 일시적인 네트워크 오류로 배치가 실패하는 상황을 방지할 수 있다.

알아볼 것

  • Spring Batch skip/retry 기능을 실제 프로젝트에 적용
  • Reader/Processor/Writer 각 단계별 오류 처리 동작 방식 이해
  • 상황별 적절한 오류 처리 전략 수립

skip

  • 잘못된 데이터를 제외하고 나머지 처리를 계속해야 할 경우

retry

  • 네트워크 오류, DB 연결 실패 등 재시도로 해결 가능한 문제일 경우

skip / retry

skip

  • 예외 발생 시 해당 아이템만 제외하고 나머지 정상 데이터는 계속 처리한다.
  • 오류 발생 → 해당 아이템 제외 → 다음 아이템 계속 처리
  • 설정한 skipLimit을 초과하면 해당 Job은 실패한다.
return new StepBuilder(STEP_NAME, jobRepository)
        .<String, String>chunk(10, transactionManager)
        .reader(simpleItemReader)
        .processor(simpleItemProcessor)
        .writer(simpleItemWriter)
        .faultTolerant()
        .skip(ValidationException.class)
        .skipLimit(10)
        .build();

retry

  • 같은 아이템에 대해 설정된 횟수만큼 재시도 후 횟수 초과 시 실패 처리한다.
  • 오류 발생 → 같은 아이템으로 재시도 → 성공 시 다음 단계 진행
  • retryLimit 초과 시 해당 아이템에 대해 실패 처리
return new StepBuilder(STEP_NAME, jobRepository)
        .<String, String>chunk(10, transactionManager)
        .reader(simpleItemReader)
        .processor(simpleItemProcessor)
        .writer(simpleItemWriter)
        .faultTolerant()
        .retry(TransientException.class)
        .retryLimit(3)
        .build();

skip

  • 각 단계에서 skip이 어떻게 동작하는지 확인해 보기위한 간단한 Job 작성
  • 전체 데이터 크기는 20, chunkSize는 5로 설정
    • 1~5: 정상 처리
    • 6~10: ItemReader에서 예외 발생 (7번에서)
    • 11~15: ItemProcessor에서 예외 발생 (13번에서)
    • 16~20: ItemWriter에서 예외 발생 (18번에서)

ItemReader

  • 예외 발생한 아이템(7번)은 즉시 건너뛰고 다음 아이템을 읽는다.
  • Processor와 Writer에는 정상 데이터만 전달된다.
// Reader에서 예외 발생 시
simpleItemReader : 6
simpleItemReader : 7 - Reader에서 예외 발생: 7  // 건너뛰고 계속 진행
simpleItemReader : 8
simpleItemReader : 9
simpleItemReader : 10
simpleItemReader : 11
simpleItemProcessor : 6
simpleItemProcessor : 8  // 7번은 처리되지 않음
simpleItemProcessor : 9
simpleItemProcessor : 10
simpleItemProcessor : 11
simpleItemWriter : [items=[6, 8, 9, 10, 11], skips=[]]

ItemProcessor

  • 예외 발생 시 해당 chunk 전체를 재처리
  • 예외 발생한 아이템만 제외하고 나머지는 정상 처리
  • Writer에는 정상 처리된 아이템만 전달한다.
// Processor에서 예외 발생 시
simpleItemReader : 12
simpleItemReader : 13
simpleItemReader : 14
simpleItemReader : 15
simpleItemReader : 16
simpleItemProcessor : 12
simpleItemProcessor : 13 - Processor에서 예외 발생: 13
// 해당 chunk를 재시도
simpleItemProcessor : 12
simpleItemProcessor : 13 - skip 처리
simpleItemProcessor : 14
simpleItemProcessor : 15
simpleItemProcessor : 16
simpleItemWriter : [items=[12, 14, 15, 16], skips=[]]  // 13번 제외

ItemWriter

  • chunk 단위 처리에서 예외 발생 시 개별 아이템 단위로 재처리
  • 예외 발생한 아이템만 skip하고 나머지는 정상 처리
  • Processor부터 다시 실행하여 Writer로 전달한다.
// Writer에서 예외 발생 시
simpleItemReader : 17
simpleItemReader : 18
simpleItemReader : 19
simpleItemReader : 20
simpleItemProcessor : 17
simpleItemProcessor : 18
simpleItemProcessor : 19
simpleItemProcessor : 20
simpleItemWriter : [items=[17, 18, 19, 20], skips=[]]
- Writer에서 예외 발생: [17, 18, 19, 20] 시도 중 18에서 예외 발생
// 이후 개별 아이템에 대해 실행
simpleItemProcessor : 17
simpleItemWriter : [items=[17], skips=[]]
simpleItemProcessor : 18
simpleItemWriter : [items=[18], skips=[]]  // 예외 발생 부분 제외
simpleItemProcessor : 19
simpleItemWriter : [items=[19], skips=[]]
simpleItemProcessor : 20
simpleItemWriter : [items=[20], skips=[]]

skip 사용 시 주의사항

  • skipLimit을 너무 높게 설정할 경우 데이터 품질이 떨어질 수 있다.
  • ItemWriter에서 skip 발생 시 개별 처리로 전환되어 성능이 저하될 수 있다.

retry

  • ItemProcessor와 ItemWriter에서만 Retry를 지원

ItemReader에서는 Retry가 동작하지 않는다.

  • Reader는 대용량 데이터의 첫 번째 관문이므로 빨라야 한다.
  • 읽기 작업 특성 상 특정 시점부터 재시도를 구현하기 어렵다.

ItemProcessor

  • 예외 발생 시 해당 chunk 전체를 재처리
  • 같은 아이템에 대해 retryLimit까지 재시도
  • 성공하면 chunk의 나머지 아이템 계속 처리
  • retryLimit 초과 시 Job 실패
// Processor에서 오류 발생 시
simpleItemReader : 11
simpleItemReader : 12
simpleItemReader : 13
simpleItemReader : 14
simpleItemReader : 15
simpleItemProcessor : 11
simpleItemProcessor : 12
simpleItemProcessor : 13
Processor attempt 1 for item 13 - Processor에서 예외 발생: 13

// 1차 재시도: chunk 전체를 다시 처리
simpleItemProcessor : 11
simpleItemProcessor : 12
simpleItemProcessor : 13
Processor attempt 2 for item 13 - Processor에서 예외 발생: 13

// 2차 재시도: chunk 전체를 다시 처리
simpleItemProcessor : 11
simpleItemProcessor : 12
simpleItemProcessor : 13
Processor attempt 3 for item 13 - 성공: 13
simpleItemProcessor : 14
simpleItemProcessor : 15

// 성공 후 Writer 작업 수행
simpleItemWriter : [items=[11, 12, 13, 14, 15], skips=[]]

ItemWriter

  • 해당 chunk의 Processor부터 재시도한다. : 같은 chunk 전체를 다시 처리
  • Writer가 성공할 때까지 전체 과정 반복
  • retryLimit 초과 시 Job 실패
// Writer에서 오류 발생 시
simpleItemReader : 16
simpleItemReader : 17
simpleItemReader : 18
simpleItemReader : 19
simpleItemReader : 20
simpleItemProcessor : 16
simpleItemProcessor : 17
simpleItemProcessor : 18
simpleItemProcessor : 19
simpleItemProcessor : 20

// 1차 Writer 시도 실패
simpleItemWriter : [items=[16, 17, 18, 19, 20], skips=[]]
Writer attempt 1 for item 18 - Writer에서 예외 발생: 18

// 1차 재시도: Processor부터 다시 실행
simpleItemProcessor : 16
simpleItemProcessor : 17
simpleItemProcessor : 18
simpleItemProcessor : 19
simpleItemProcessor : 20
simpleItemWriter : [items=[16, 17, 18, 19, 20], skips=[]]
Writer attempt 2 for item 18 - Writer에서 예외 발생: 18

// 2차 재시도: Processor부터 다시 실행
simpleItemProcessor : 16
simpleItemProcessor : 17
simpleItemProcessor : 18
simpleItemProcessor : 19
simpleItemProcessor : 20
simpleItemWriter : [items=[16, 17, 18, 19, 20], skips=[]]
Writer attempt 3 for item 18 - 성공

주의 사항

  • Processor와 Writer는 여러 번 실행되어도 같은 결과를 보장해야 한다.
  • chunk 전체가 재처리되므로 큰 chunk는 성능에 영향을 줄 수 있다.

어떻게 적용할까

skip과 retry를 함께 사용해 더욱 견고한 배치 시스템을 구축할 수 있다.

  • retry : 해결 가능한 오류는 먼저 재시도
  • skip : 재시도로도 해결되지 않는 데이터 품질 문제는 건너뛰기
return new StepBuilder(STEP_NAME, jobRepository)
        .<String, String>chunk(jobParameter.getChunkSize(), transactionManager)
        .reader(simpleItemReader)
        .processor(simpleItemProcessor)
        .writer(simpleItemWriter)
        .faultTolerant()
				// retry 설정: 일시적 오류에 대한 재시도
        .retry(TransientDataAccessException.class)
        .retry(ConnectException.class)
        .retry(SocketTimeoutException.class)
        .retryLimit(3)
        // skip 설정: 데이터 품질 문제는 건너뛰기
        .skip(IllegalArgumentException.class)
        .skip(NumberFormatException.class)
        .skip(ValidationException.class)
        .skipLimit(50)
        .build();

Skip 선택 기준:

  • 데이터 자체에 문제가 있어 수정이 불가능한 경우
  • 일부 데이터 손실이 비즈니스적으로 허용 가능한 경우
  • 전체 처리 완료가 개별 데이터 정확성보다 중요한 경우

Retry 선택 기준:

  • 외부 시스템 의존성으로 인한 일시적 오류
  • 시간이 지나면 해결될 가능성이 높은 문제
  • 데이터 손실이 허용되지 않는 경우
반응형