티스토리 뷰

Spring Batch의 기본적인 기능들을 지난 포스팅들을 통해 알아보았다. 이번에 알아볼 Skip/Retry도 Spring Batch에서는 기본적으로 제공을 하는 기능이다. 간단한 예제를 통해서 알아보자. 


us-500.csv (웹에서 쉽게 구할수 있음)

Chunk Example을 볼때 잠깐 나왔었던 500 row의 csv 파일이다. 테스트를 위해 사용이 될 것이고 Chunk 방식의 Batch를 사용해 이 파일을 읽어서 가공하고 DB에 쓸 것이다. 테스트는 파일을 가공하는 단계(Processor)에서 강제로 오류를 내고 그것을 잘 skip하는지 또는 잘 retry 하는지 살펴볼것이다. 여기서는 일단 Art라는 사람과 Donette라는 사람이 있다는것 정도 알아두자. 


Spring Batch Skip Example

SampleSkipretry.java

@Configuration
public class SampleSkipretry {

    @Autowired
    public JobBuilderFactory jobBuilderFactory;

    @Autowired
    public StepBuilderFactory stepBuilderFactory;
    
    @Bean
    public Job sampleSkipretryJob(SampleJobListener jobListener, Step sampleSkipretryStep) {
        return jobBuilderFactory.get("sampleSkipretryJob")
            .incrementer(new RunIdIncrementer())
            .listener(jobListener)
            .flow(sampleSkipretryStep)
            .end()
            .build();
    }

    @Bean
    public Step sampleSkipretryStep(SampleStepListener stepListener, JdbcBatchItemWriter<Employee> writer) {
        return stepBuilderFactory.get("sampleSkipretryStep")
            .listener(stepListener)
            .<Employee, Employee> chunk(10)
            .reader(reader())
            .processor(processor())
            .writer(writer)
            .faultTolerant()
            .skipLimit(2)
            .skip(FileNotFoundException.class)
            .skip(SQLException.class)
            .noSkip(MalformedInputException.class)
    //      .skipPolicy(new UserSkipPolicy())
            .build();
    }
    
    @Bean
    public EmployeeLowerProcessor processor() {
        return new EmployeeLowerProcessor();
    }
    
    //reader, writer 생략
    
}

간단한 Chunk 방식의 Batch에 Skip을 위한 샘플 코드를 추가하였다. 눈여겨 보아야 할 부분은 sampleSkipRetryStep이라는 step이다. Skip, Retry는 Step 단계에서 정의할 수 있다. Skip, Retry를 사용하기 위해서는 우선 faultToLerant()라는 메소드를 사용해야 한다. 그 다음에 쓰이는 skip 관련된 메소드들에 대한 설명은 다음과 같다. 

 

  • skipLimit() : skip 허용 회수. 허용 회수를 넘어가면 job은 실패한다. skip()과 반드시 같이 써야 한다. 
  • skip() : 해당 exception이 발생했을때 skip을 하겠다는 것이다. 
  • noSkip() : 해당 exception이 발생하면 skip을 하지 않고 오류를 내겠다는 것이다. 
  • skipPolicy() : 사용자 정의로 skip에 대한 policy를 만들어서 적용하고 싶을때 사용한다. 

 

 

즉 위의 예제는 FileNotFoundException, MalformedInputException (필자 맘대로 아무 Exception 사용)이 배치 수행중 발생하면 2번까지는 skip 하고 그 뒤로는 오류를 발생시키겠다. 라는 것이다. 

 

테스트를 한번 해보자. processor에서 강제로 특정 항목에 대해서 오류를 낼 것이다. 특정 항목이라 함은 위의 csv 파일중 Art와 Donette를 말하며 이들을 처리할때 강제로 오류를 낼것이다. 

 

EmployeeLowerProcessor.java

public class EmployeeLowerProcessor implements ItemProcessor<Employee, Employee> {

    @Override
    public Employee process(final Employee employee) throws Exception {
        final String first_name = employee.getFirst_name().toLowerCase();
        // 생략
        
        if(first_name.equalsIgnoreCase("Art")) {
            throw new SQLException("################ forced error 1 ####################");
        } 
        
        if(first_name.equalsIgnoreCase("Donette")) {
            throw new FileNotFoundException("################ forced error 2 ####################");
        } 
        
        final Employee lowerEmployee = new Employee(first_name, last_name, company_name, address, city, county, state, zip, phone1, phone2, email, web);

        return lowerEmployee;
    }
}

reader가 csv파일을 읽었고 그걸 processor가 처리하는 과정에서 Art와 Donette가 나오면 각각 오류를 내는 processor이다. 오류는 2건이 발생이 되고 위의 설정대로라면 skipLimit는 2건이고 SQLException과 FileNotFoundException은 skip을 하기로 했으므로 Batch를 실행시키면 정상 수행이 된다. 2건에 대해 정상 skip이 된다. 

 

그럼 실패하는 경우를 보자. 

    .faultTolerant()
    .skipLimit(1)
    .skip(FileNotFoundException.class)
    .skip(SQLException.class)

processor 는 그대로 두고 step 정의부에서 skipLimit을 1로 변경했다. 

제한은 1이라 첫번째 오류는 skip이 되었는데 두번째 오류는 skip되지 못했다. 

 

    .faultTolerant()
    .skipLimit(2)
    .skip(FileNotFoundException.class)

제한을 2로 두었고 SQLException은 skip 하는걸 제거했다. 

이렇듯 이번에는 먼저 들어오는 Art가 SQLException을 내다보니 그걸 받아주는 부분이 없어 skip되는 부분 없이 바로 오류가 나타난다. 

 

이런 식으로 조건을 변경해가며 skip에 대한 정의를 할 수 있다. 

 


Spring Batch Retry Example

Skip을 이해했다면 Retry는 더욱 쉽게 이해할 수 있다. 사용방법이 거의 동일하기 때문이다.

    @Bean
    public Step sampleSkipretryStep(SampleStepListener stepListener, JdbcBatchItemWriter<Employee> writer) {
        return stepBuilderFactory.get("sampleSkipretryStep")
            .listener(stepListener)
            .<Employee, Employee> chunk(10)
            .reader(reader())
            .processor(processor())
            .writer(writer)
            .faultTolerant()
            .retryLimit(1)
            .retry(SQLException.class)
            .noRetry(MalformedInputException.class)
     //     .retryPolicy(new UserRetryPolicy())
            .build();
    }

위와 다른 부분은 중복이 되어서 모두 생략하고 step에서 retry 정의하는 부분만 코드를 넣었다. skip과 마찬가지로 retry도 관련 메소드들이 무슨 일을 하는지 알고 넘어가자. 

 

  • retryLimit() : 재시도 회수. 정한 값을 넘어가면 더이상 재시도를 하지 않고 실패처리한다. 
  • retry() : 해당 exception이 발생했을때 retry를 하겠다는 것이다. 
  • noRetry() : 해당 exception이 발생했을때 retry를 하지 않겠다는 것이다.
  • retryPolicy() : 사용자 정의로 retry에 대한 policy를 만들어서 적용하고 싶을때 사용한다. 

 

 

 

Skip과 거의 동일하다. 위 예제는 SQLException이 발생하면 최대 한번 재시도 해보겠다는 뜻이다. 

필자는 아무 Exception을 박아 넣었는데 보통은 ConnectTimeoutException이나 DeadlockLoserDataAccessException 같은 재시도를 해봄직한 Exception인 경우에 retry를 하는 의미가 있다. 이 외에는 100번 실패해서 100번을 돌려도 어차피 결과는 실패일테니까 말이다. 

 

테스트를 해보자. processor는 동일한것을 사용한다. 

 

아마도 테스트를 하면 skip 을 했을때랑 동일한 exception이 발생할 것이다. 

 

Non-skippable exception in recoverer while processing.. 필자도 테스트를 하며 이부분에서 많은 시간을 허비하였다.  분명 실행할때의 생각으로는 오류를 retryLimit에 지정한 수만큼 뱉는 로그가 반복되어야 한다고 생각을 했는데 매번 저 오류만 나오며 한번 실패한 것에 대한 오류만 나왔다. 기능이 정상동작 하지 않는건가.. 하는 생각으로 옵션을 이렇게 저렇게 바꿔 보았지만 결과는 매번 똑같았다. 어떻게 해서도 안되길래 retryLimit을 엄청나게 올려 버렸더니 배치가 계속 수행이 된다. 디버깅을 해봤더니 내부적으로 retry를 계속 시도하고 그래도 처리가 안되는 경우만 Non-skippable exception in recoverer while processing.. 이 오류가 최종적으로 나온다. retry가 정상동작하는것이다. 위에서 얘기한대로 ConnectTimeoutException이나 DeadlockLoserDataAccessException 에 대한 테스트를 수행하지 않아서 발생을 한것 같다. 역시 정확한 테스트 환경을 구축하고 해야하는것 같다. 

 

끝!

댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31