JaeWon's Devlog
article thumbnail
반응형

이전 글 에서는 간단하게 Spirng Batch 환경을 구성하고 동작하는 것을 확인해보았습니다.

이번 글에서는 Spring Batch 의 기능 중 Step 에 대해 활용하여 사용해보려고 합니다.


1. 0. 다양한 Step 설정

- 해당 테스트를 위해 간단하게 controller 를 선언하여 배치를 수행하도록 추가하겠습니다.

<java />
@Controller @Slf4j @RequiredArgsConstructor public class TestController { private final Job StepTestJob; private final JobLauncher jobLauncher; @SneakyThrows @GetMapping("/test/{number}") public void test(@PathVariable Long number){ log.info(number + " 번 째 실행"); jobLauncher.run(StepTestJob, new JobParameters()); } }

2. 1. Step 에서 startLimit 사용

- 만약 외부와의 통신 또는 DB 작업 도중 연결(커넥션)이 끊기면서 작업이 실패하는 경우가 있을 수 있습니다.

- 이러한 경우 해당 배치를 재시작해야 하는데, 기본적으로 재시작 가능 횟수는 1회로 설정되어 있습니다.

- SpringBatch 는 startLimit() 으로 재시작 횟수를 지정할 수 있습니다.

- 만약 startLimit() 에 지정한 횟수보다 더 이후 실행에서는 Exception 이 발생합니다.

<java />
@Slf4j @Configuration @RequiredArgsConstructor public class StepTestBatchJob { private final JobBuilderFactory jobBuilderFactory; private final StepBuilderFactory stepBuilderFactory; @Bean public Job simpleJob() { return jobBuilderFactory.get("simpleJob") .start(simpleStep1()) .build(); } @Bean @JobScope public Step simpleStep1() { return stepBuilderFactory.get("simpleStep1") .startLimit(3) // 재시작 3번 가능 .tasklet((contribution, chunkContext) -> { log.info(">>>>> This is SimpleStep1"); // 에러 발생 int error = 5/0; return RepeatStatus.FINISHED; }) .build(); } }

- 기본적으로 실행시 ArithmeticException 이 발생하도록 하여 테스트하였습니다.

- 매번 실행할 때마다 해당 Exception 이 발생하게 됩니다.

- 만약 4번 째 재시도 시에는 StartLimitExceededException 이 발생하는 것을 확인하실 수 있습니다.

3. 2. Step 에서 Skip 사용

- Skip 은 데이터를 처리하는 동안 설정된 Exception 이 발생한 경우, 해당 데이터 처리를 건너뛰는 기능입니다.

- 데이터의 작은 오류에 대해서 Step 의 실패 처리 대신 Skip 을 함으로써, 배치 수행의 빈번한 실패를 줄일 수 있습니다.

- 만약 skipLimit() 에 지정한 횟수보다 더 이후 실행에서는 Exception 이 발생합니다.

https://backtony.github.io/spring/2022-01-28-spring-batch-10/#api

  • ItemReader
    - item 을 읽던 도중 예외가 발생하게 되면 해당 item 을 skip 하고 다음 item 을 읽습는다.
  • ItemProcessor
    - item 을 처리하던 도중 예외가 발생하면 해당 Chunk 의 첫 단계로 돌아가서 itemReader로부터 다시 데이터를 전달받습니다.
    - itemProcessor 는 다시 item 을 받아 실행하는데 이전 실행에서 발생한 예외 정보가 내부적으로 남아있어 위의 그림처럼 item2 의 차례가 온다면 처리하지 않고 넘어갑니다.
  • itemWriter
    - Writer 에서 예외가 발생하게 되면 다시 Chunk 단위로 ItemReader 로 돌아갑니다.

https://backtony.github.io/spring/2022-01-28-spring-batch-10/#api

<java />
@Slf4j @Configuration @RequiredArgsConstructor public class StepTestBatchJob extends MemberProcessor { private final JobBuilderFactory jobBuilderFactory; private final StepBuilderFactory stepBuilderFactory; @Bean public Job simpleJob() { return jobBuilderFactory.get("simpleJob1") .start(simpleStep1()) .build(); } @Bean public Step simpleStep1() { return stepBuilderFactory.get("simpleStep1") .<String, String>chunk(5) .reader(reader()) .processor(processor()) .writer(writer()) .faultTolerant() // 내결함성 기능 활성화 .skipLimit(2) // skip 허용 횟수, 해당 횟수 초과 시 Error 발생, Skip 사용시 필수 선언 .skip(ArithmeticException.class) // ArithmeticException 에 대해서는 skip .skip(SQLException.class) // SQLException 에 대해서는 skip .noSkip(NullPointerException.class) // NullPointerException 에 대해서는 skip 하지 않음 //.skipPolicy(new CustomPolicy) // 사용자가 커스텀하여 Skip Policy 설정 가능 .build(); } // writer, read, processor 는 아래 코드 참고 .... }

- read, writer, processor 에서 각각의 skip 을 확인해보겠습니다.

3.1. 2-1. ItemReader 에서 Skip

<java />
@Bean public ItemReader<String> reader() { log.info("ItemReader 에서는 데이터를 불러오는 로직 작성"); return new ItemReader<String>() { int i = 0; @SneakyThrows @Override public String read() throws ArithmeticException { i++; if (i == 2){ log.error("ItemReader ArithmeticException 발생"); throw new ArithmeticException("에러 발생"); } if (i == 4){ log.error("ItemReader SQLException 발생"); throw new SQLException("에러 발생"); } log.info("itemReader >>> " + i); return i > 20 ? null : String.valueOf(i); } }; }

- 2번째 데이터를 읽을 때 ArithmeticException 예외가 발생하고, 4번째 데이터를 읽을 때도 SQLException 이 발생하지만 skipLimit(2) 이므로 skip 하고 진행하게 됩니다.(skipLimit 가 1인 경우 4번째 데이터를 읽을 때 Exception 이 발생합니다.)

- Chunk 사이즈가 5 이기 때문에 첫 번째 읽기 작업에서는 1,3,5,6,7 이후 다음 작업으로 넘어가게 됩니다.

- 만약 중간에 i == 3 인 경우에 NullPointerException 이 발생하게 시킨다면, noSkip 에 따라 Exception 이 발생합니다.

<java />
@Bean public ItemReader<String> reader() { log.info("ItemReader 에서는 데이터를 불러오는 로직 작성"); return new ItemReader<String>() { int i = 0; @SneakyThrows @Override public String read() throws ArithmeticException { i++; if (i == 2){ log.error("ItemReader ArithmeticException 발생"); throw new ArithmeticException("에러 발생"); } if (i == 3){ log.error("ItemReader NullPointerException 발생"); throw new NullPointerException("에러 발생"); } if (i == 4){ log.error("ItemReader SQLException 발생"); throw new SQLException("에러 발생"); } log.info("itemReader >>> " + i); return i > 20 ? null : String.valueOf(i); } }; }

3.2. 2-2. ItemProcessor 에서 Skip

<java />
@Bean public ItemProcessor<String, String> processor() { log.info("ItemProcessor 에서는 데이터를 처리 하는 로직 작성"); return item -> { log.info("itemPrcoessor >>> " + item); if(item.equals("3")){ log.error("ItemProcessor 에러 발생"); throw new SQLException(); } return item; }; }

- 3번 째 데이터를 처리할 때 SQLException 이 발생하지만, 해당 Exception 은 Skip 하도록 되어 있으니 넘어가게 됩니다.

- 그러나, 넘어가더라도 데이터 처리를 위해 재동작을 하게 되는데 이때, ItemReader 는 캐싱된 데이터를 다시 itemProcssor 로 넘기므로 로그는 다시 찍히지 않습니다.

- 로그에서와 같이 itemProcessor 가 다시 Chunk 단위로 재시작 되는 것을 확인하실 수 있습니다.

3.3. 2-3. ItemWriter 에서 Skip

<java />
@Bean public ItemWriter<String> writer() { log.info("ItemWriter 에서는 DB 저장과 같은 Transactional 한 로직 작성"); return items -> { for (String item : items){ if(item.equals("3")){ log.error("ItemWriter 에러 발생"); throw new SQLException(); } } log.info("items => " + items); }; }

- 3번 째 데이터를 처리할 때 SQLException 이 발생하지만, 해당 Exception 은 Skip 하도록 되어 있으니 넘어가게 됩니다.

- 그러나, 넘어가더라도 데이터 처리를 위해 재동작을 하게 되는데 이때, ItemProcessor 는 ItemWriter 로 리스트가 아니라 한 건씩만 보내서 처리하는 것을 확인하실 수 있습니다.

 

4. 3. Step 에서 Retry 사용

- Retry 는 위에 Skip 과 크게 다르지 않습니다.

- Skip 은 넘어간다면, Retry 는 재시도를 하게 됩니다.

- Skip 과 다르게 ItemReader 에서는 지원하지 않으며, ItemProcessor, ItemWriter 에서만 Retry 가 가능합니다.

- Retry Count 는 Item 마다 각각 존재합니다.

아래 예제에서는 Retry 시 무조건 예외가 발생하게 됩니다.(무조건 throw 를 하기 때문...)
실무에서는 서버 간 통신이 중간에 끈기 거나 그런 경우에 재시도하여 정상 통신되는 경우를 대비해 Retry 를 사용하시면 될 것으로 생각됩니다.

https://backtony.github.io/spring/2022-01-28-spring-batch-10/#api

  • ItemProcessor
    - 예외가 발생한다면 다시 Chunk 처음부터 처리합니다.
  • ItemWriter
    - Skip 과 다르게 원래대로 List 로 한 번에 처리합니다.

https://backtony.github.io/spring/2022-01-28-spring-batch-10/#api

<java />
@Slf4j @Configuration @RequiredArgsConstructor public class StepTestBatchJob extends MemberProcessor { private final JobBuilderFactory jobBuilderFactory; private final StepBuilderFactory stepBuilderFactory; @Bean public Job simpleJob() { return jobBuilderFactory.get("simpleJob1") .start(simpleStep1()) .build(); } @Bean public Step simpleStep1() { return stepBuilderFactory.get("simpleStep1") .<String, String>chunk(5) .reader(reader()) .processor(processor()) .writer(writer()) .faultTolerant() // 내결함성 기능 활성화 .retry(SQLException.class) // SQLException에 대해서는 Retry .retryLimit(2) // Retry 횟수 .build(); } @Bean public ItemReader<String> reader() { log.info("ItemReader 에서는 데이터를 불러오는 로직 작성"); return new ItemReader<String>() { int i = 0; @SneakyThrows @Override public String read() throws ArithmeticException { i++; log.info("itemReader >>> " + i); return i > 10 ? null : String.valueOf(i); } }; } // writer, processor 는 아래 코드 참고 }

4.1. 3-1. ItemProcessor 에서 Retry

<java />
@Bean public ItemProcessor<String, String> processor() { log.info("ItemProcessor 에서는 데이터를 처리 하는 로직 작성"); return item -> { log.info("itemPrcoessor >>> " + item); if(item.equals("4")) { log.error("ItemProcessor 에서 SQLException 발생"); throw new SQLException(); } return item; }; }

- 4번 째 데이터를 처리할 때 SQLException 이 발생하지만, 해당 Exception 은 Retry 하도록 되어 있으니 재시도하게 됩니다.

- Skip 과 동일하게 ItemReader 를 사용하기에 로그에는 찍히지 않습니다.(코드상에서는 주석 처리함...)

- 로그에서와 같이 itemProcessor 가 다시 Chunk 단위로 재시작되는 것을 확인하실 수 있습니다.

- RetryLimit(2) 로 세 번째 재시도 시에도 Excepion 이 발생하게 되면서 RetryException 이 최종적으로 발생합니다.

 

4.2. 3-2. ItemWriter 에서 Retry

<java />
@Bean public ItemWriter<String> writer() { log.info("ItemWriter 에서는 DB 저장과 같은 Transactional 한 로직 작성"); return items -> { for (String item : items) { if (item.equals("4")) { log.error("ItemWriter 에서 SQLException 발생"); throw new SQLException(); } } log.info("items => " + items); }; }

- 3번 째 데이터를 처리할 때 SQLException 이 발생하지만, 해당 Exception 은 Retry 하도록 되어 있으니 재시도하게 됩니다.

- 그러나, Skip 과 다르게 재시작되어도 Processor 에서 한 개씩 보내는 게 아닌 List 로 한 번에 처리하게 됩니다.


참고

- https://khj93.tistory.com/entry/Spring-Batch%EB%9E%80-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

- https://jojoldu.tistory.com/331?category=902551

- https://oingdaddy.tistory.com/183

- https://backtony.github.io/spring/2022-01-28-spring-batch-10/

반응형
profile

JaeWon's Devlog

@Wonol

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!