Junuuu 2023. 10. 12. 00:01
반응형

개요

Spring Batch를 실행하다가 예외가 발생하면 어떻게 되는지 알아보고, 안정적인 Batch Application을 만들기 위해서 어떤 방안이 있는지 알아보고자 합니다.

 

예외가 간헐적으로 발생하는 Batch Application 구성

@Profile("skip-job")
@Configuration
class SkipJobConfig(
    private val jobRepository: JobRepository,
    private val batchTransactionManager: PlatformTransactionManager,
) {
    @Bean
    fun skipJob(): Job {
        return JobBuilder("skipJob job", jobRepository)
            .incrementer(RunIdIncrementer())
            .start(chunkStep())
            .build()
    }

    @Bean
    fun chunkStep(): Step {
        return StepBuilder("skipJob step", jobRepository)
            .chunk<String, String>(BATCH_SIZE, batchTransactionManager)
            .reader(beanReader())
            .writer(ClassItemWriter())
            .build()
    }

    @Bean
    fun beanReader(): ItemReader<String> {
        val data: List<String> = mutableListOf(
            "Byte",
            "Code",
            "Data",
            "Disk",
            "File",
            "Input",
            "Loop",
            "Logic",
            "Mode",
            "Node",
            "Port",
            "Query",
            "Ratio",
            "Root",
            "Route",
            "Scope",
            "Syntax",
            "Token",
            "Trace"
        )
        logger.info ("Reading item: {}", data)
        return ListItemReader(data)
    }

    class ClassItemWriter: ItemWriter<String> {
        override fun write(itmes: Chunk<out String>) {
            logger.info("Writing item start")
            itmes.forEach {
                logger.info("Writing item : {}", it)
                if(it.contains('o')){
                    throw IllegalArgumentException("문자에 o가 들어가는 경우 예외를 만들면 어떻게 될까?")
                }
            }
            
        }
    }

    companion object {
        private const val BATCH_SIZE = 5
    }
}

예외와 함께 batch_job_execution table에 실패로 기록됩니다.

 

특정 예외가 발생했을 때 Skip 처리

    @Bean
    fun chunkStep(): Step {
        return StepBuilder("skipJob step", jobRepository)
            .chunk<String, String>(BATCH_SIZE, batchTransactionManager)
            .reader(beanReader())
            .writer(ClassItemWriter())
            .faultTolerant()
            .skip(IllegalArgumentException::class.java)
            .skipLimit(20)
            .build()
    }

오류가 발생할 경우 장애를 처리하기 위한 기능을 제공하는 faultTolerant()를 선언합니다.

그리고 어떤 예외에서 skip을 수행할지 몇 번까지 허용할지에 대한 정책을 결정합니다.

위의 코드에서는 IllegalArgumentException에 대한 예외는 Skip 하고 최대 20번까지 허용합니다.

이제 더이상 오류가 발생해도 Step이 즉시 종료되지 않고 20번까지는 예외를 허용합니다.

 

작업을 수행하고 batch_step_execution 테이블의 데이터를 보면 11번의 write_skip_count를 가지며 작업은 성공적으로 끝났음을 알 수 있습니다.

 

예외를 핸들링하는 SkipPolicy 구현

@Configuration
class CustomSkipPolicy : SkipPolicy {
    
    companion object {
        private const val MAX_SKIP_COUNT = 10
    }

    override fun shouldSkip(throwable: Throwable, skipCount: Long): Boolean {
        if (throwable is IllegalArgumentException && skipCount < MAX_SKIP_COUNT) {
            return true
        }
        
        return false
    }
}

@Bean
fun chunkStep(): Step {
    return StepBuilder("skipJob step", jobRepository)
        .chunk<String, String>(BATCH_SIZE, batchTransactionManager)
        .reader(beanReader())
        .writer(ClassItemWriter())
        .faultTolerant()
//      .skip(IllegalArgumentException::class.java)
//      .skipLimit(20)
        .skipPolicy(CustomSkipPolicy())
        .build()
}

조금 더 정교한 SkipPolicy를 위해서 Spring Batch에서는 SkipPolicy 인터페이스를 제공합니다.

Skip 조건을 직접 구현할 수 있습니다.

 

이번에는 10번 허용하도록 해보겠습니다.

위에서는 11번의 Skip이 발생했으므로 CustomSkipPolicy를 적용하게 되면 Step은 실패해야 합니다.

 

예상대로 실패하게되고 SKIP_COUNT를 20으로 늘리면 다시 성공합니다.

 

 

 

참고자료

https://www.baeldung.com/spring-batch-skip-logic