You will be fine

<Spring Batch> 6. Exception에 대한 skip 처리하기

by BFine
반응형

가. Batch Fault 

 a. Exception

  -  배치는 보통 대용량 데이터를 처리하는 경우가 많다. 그렇기 떄문에 그에 따라 다양한 예외사항이 발생할수도 있다.

  -  즉 Exception 중에서 Job을 중단 해야할 정도의 크리티컬한 경우와 그렇지 않은 경우가 있을 수 있다.

  -  Spring Batch에서는 이부분에 대한 클래스와 메서드를 지원하고 있고 간단하게 사용할수가 있도록 제공하고 있다.

 

나. Skip 처리하기

 a. faultTolerant 

  -  Step을 만들때 .faultTolerant 메서드를 볼 수가 있다. 이름처럼 Step에 대한 falut를 허용한다라는 의미를 지니고 있다.

  -  자세히 살펴보면  다양한 메서드들을 제공하고 있는 것을 볼 수 있다. 

 

 b. skip 과 skipLimit

  -  먼저 Exception을 임의로 발생하도록 만들어보고 결과를 살펴보자

@Component
public class AItemProcessor implements ItemProcessor<Input,Output> {
    @Override
    public Output process(Input item) throws Exception {
        System.out.println("## A 처리 - "+item);
        throw new IllegalArgumentException("임의로 예외발생 시키기");
    }
}

  - Step에서 예외가 발생하였고 그에 따라서 Job은 종료되고 프로세스도 내려가는 것을 볼수 있다.

 -  이제 이 Exception을 skip 하도록 설정해보자. skipLimit는 해당 Exception을 얼마나 봐줄지 지정해주는 부분이다.

@Bean
public Step step(ItemReader<Input> jpaPagingItemReader, ItemWriter<Output> jpaItemWriter, ItemProcessor<Input,Output> itemProcessor){

    return stepBuilderFactory.get("Chunk")
            .<Input,Output>chunk(10)
            .reader(jpaPagingItemReader)
            .processor(itemProcessor)
            .writer(jpaItemWriter)
            .faultTolerant()
                .skip(IllegalArgumentException.class).skipLimit(3)
            .build();
}

Exception이 발생해서 Job은 중단되었지만 설정하기전과 다르게 3번까지는 skip 한 것을 볼 수 있다.  

 

 c. 여러 Exception skip

  -  배치처럼 대용량 데이터를 처리하는 경우 skip 해야할 Exception이 하나가 아닐 수 있다. 여러 개의 Exception을 추가 해보자

@Component
public class OtherItemProcessor implements ItemProcessor<Input,Output> {

    @Override
    public Output process(Input item) throws Exception {
        System.out.println("## Other - "+item);
        throw new IllegalAccessException("임의로 예외발생 시키기");
    }
}
@Bean
public Step step(ItemReader<Input> jpaPagingItemReader, ItemWriter<Output> jpaItemWriter, ItemProcessor<Input,Output> itemProcessor){

    return stepBuilderFactory.get("Chunk")
            .<Input,Output>chunk(10)
            .reader(jpaPagingItemReader)
            .processor(itemProcessor)
            .writer(jpaItemWriter)
            .faultTolerant()
                .skip(IllegalAccessException.class)
                .skip(IllegalArgumentException.class)
                .skipLimit(2)
            .build();
}

  -  똑같이 예외를 발생시키는 부분을 추가했고 그리고 단순히 skip에 Exception을 추가했다. 로그를 살펴보자

 -  보면 OtherProcesser는 2번 실행되었는데 왜 skip limit가 초과했지? 3번째에 예외발생해야하는 것 아닌가라는 생각이 들수있다.

 -  FaultTolerantStepBuilder 내부 코드를 살펴보자

  -  코드를 보면 HashMap을 이용하여 skip class를 담는것을 볼 수 있고 skipLimit는 int인 것을 알수 있다.

     => 즉 위의 skip은 하나의 그룹으로 묶이고 각각의 발생횟수의 합계가 skipLimit를 넘으면 예외가 발생한다.

@Bean
public Step step(ItemReader<Input> jpaPagingItemReader, ItemWriter<Output> jpaItemWriter, ItemProcessor<Input,Output> itemProcessor){

    return stepBuilderFactory.get("Chunk")
            .<Input,Output>chunk(10)
            .reader(jpaPagingItemReader)
            .processor(itemProcessor)
            .writer(jpaItemWriter)
            .faultTolerant()
                .skip(IllegalAccessException.class).skipLimit(5)
                .skip(IllegalArgumentException.class).skipLimit(2)
            .build();
}

  -  혹시나 위와 같이 처리하면 뭔가 될 것 같다(?)라는 생각이 들수도 있지만 안된다는 것을 조심하자(실제 경험에서 나온 것은 비밀..) 

 

 d. 개별적으로 Exception skip 하기

  -  위와 다르게 각각의 Exception에 대해 skip 처리해야하는 경우도 있다. 그 전에 SkipPolicy를 살펴보자

  -  SkipPolicy는 인터페이스명 그대로 어떤식으로 skip 처리 할지를 정책을 정하는 부분이다. 이미지에 보이는 것처럼 5개의 구현체들이 있다.

 -  아쉽게도 위의 구현체들로는 개별적인 Exception count를 할 수 없기 때문에 따로 만들어야 한다. 클래스를 만들어보자

public class CustomSkipPolicy implements SkipPolicy {

    private final Map<Class<? extends Throwable>,Integer> exceptionClassMap = new HashMap<>();
    private final Map<Class<? extends Throwable>,Integer> exceptionCountMap = new HashMap<>();

    public void setExceptionClassMap(Class<? extends Throwable> clazz, int limit){
        exceptionClassMap.put(clazz,limit);
    }

    @Override
    public boolean shouldSkip(Throwable t, int skipCount) throws SkipLimitExceededException {

        if(exceptionClassMap.containsKey(t.getClass())){

            int limit = exceptionClassMap.get(t.getClass());
            int count = exceptionCountMap.getOrDefault(t.getClass(),0);

            if(count+1 > limit){
                throw new SkipLimitExceededException(limit,t);
            }

            exceptionCountMap.put(t.getClass(), count+1);

            return true;

        }
        return false;
    }
}

  -  여기서 주의해야하는 부분은 skipCount는 모든 Exception 발생에 대해서 count하기 때문에 여기서는 큰 의미를 가지지 않는다.

  => 번외로 비동기 처리를 하는 경우에  skipCount는 스레드마다 개별적으로 값을 가진다.

@Bean
public Step step(ItemReader<Input> jpaPagingItemReader, ItemWriter<Output> jpaItemWriter, ItemProcessor<Input,Output> itemProcessor){

    return stepBuilderFactory.get("Chunk")
            .<Input,Output>chunk(10)
            .reader(jpaPagingItemReader)
            .processor(itemProcessor)
            .writer(jpaItemWriter)
            .faultTolerant()
                .skipPolicy(skipPolicy())
            .noRetry(Exception.class)
            .build();
}

public SkipPolicy skipPolicy(){
    CustomSkipPolicy skipPolicy = new CustomSkipPolicy();
    skipPolicy.setExceptionClassMap(IllegalArgumentException.class,3);
    skipPolicy.setExceptionClassMap(IllegalAccessException.class,3);
    return skipPolicy;
}

  -  테스트 해보면 설정한대로 limit가 발생한 것을 볼 수 있다. 이런식으로 SkipPolicy 같은 경우 custom하게 처리가 가능하다.

반응형

블로그의 정보

57개월 BackEnd

BFine

활동하기