Spring Batch Step : Tasklet, Chunk

이종찬·2025년 7월 28일
post-thumbnail

Spring Batch에서 Job은 하나 이상의 Step으로 구성됩니다. **Step은 배치 작업의 실질적인 처리 단계를 정의하는 독립적인 실행 단위입니다. 예를 들어 "파일을 읽고 유효성을 검증하는 단계", "데이터를 가공하여 DB에 저장하는 단계", "작업 완료 후 리소스를 정리하는 단계" 등이 모두 개별적인 Step으로 구현될 수 있습니다.

Step의 가장 대표적인 구현체는 TaskletStep입니다. TaskletStep은 특정 로직을 반복해서 수행하며, 이 로직을 구현하는 방식에 따라 크게 두 가지 모델로 나뉩니다.

  1. 단순 Tasklet 모델: 하나의 큰 작업(Task)을 한 번에 처리하는 방식
  2. Chunk 지향 모델: 대용량 데이터를 덩어리(Chunk)로 나누어 처리하는 방식

이번 글에서는 두 모델의 특징과 사용법을 알아보겠습니다.

간단한 단일 작업 - Tasklet

Step을 만드는 가장 간단한 방법은 Tasklet을 이용하는 것입니다.

Tasklet단일 작업을 수행하는 간단한 컴포넌트입니다. 대용량 데이터를 한 건씩 처리하는 것이 아니라, Step 내에서 한 번만 실행될 독립적인 로직을 담는 데 사용됩니다.

TaskStep에 의해 반복적으로 수행되며 반환값(RepeatStatus)에 따라 계속 수행, 종료 됩니다.

주로 다음과 같은 작업에 적합합니다.

  • Job 실행 전 사전 준비: 특정 파일이 있는지 확인하거나, 임시 테이블의 데이터를 비우는(TRUNCATE) 작업.
  • Job 실행 후 마무리 작업: 임시 파일을 삭제하거나, 작업 완료 알림 메일을 보내는 작업.
  • 간단한 쿼리 실행: 특정 테이블의 상태 값을 한 번에 업데이트하는 등의 단일 쿼리 실행.

Tasklet

@Slf4j  
public class TaskletSample implements Tasklet {  
  
    @Override  
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {  
        log.info("tasklet sample");  
        return RepeatStatus.FINISHED;  
    }  
}

BatchConfig

@Slf4j  
@RequiredArgsConstructor  
@Configuration  
public class BatchConfig {  
  
    private final JobRepository jobRepository;  
    private final PlatformTransactionManager transactionManager;  
  
    @Bean  
    public Job job() {  
        return new JobBuilder("job", jobRepository)  
                .start(step1())  
                .build();  
    }  
  
    @Bean  
    public Step step1() {  
        return new StepBuilder("step1", jobRepository)  
                .tasklet(new TaskletSample(), transactionManager)  
                .build();  
    }  
}

대용량 데이터 처리 - Chunk

데이터를 한 건씩 처리해서 DB에 저장하는 대신, 일정량의 묶음(Chunk) 단위로 모아서 한 번에 처리하고, 트랜잭션도 한 번만 사용하는 방식입니다.

대부분의 배치 작업은 '데이터를 읽어서(Read), 가공한 뒤(Process), 저장(Write)'하는 형태로 이루어집니다. StepBuilder는 이 패턴을 아주 효율적이고 안전하게 처리할 수 있도록 Chunk(청크) 모델을 제공합니다.

이 모델은 세 가지 주요 컴포넌트로 구성됩니다.

  • ItemReader<I>: 데이터 소스(파일, DB 등)에서 데이터를 한 건씩 읽어오는 역할.
  • ItemProcessor<I, O>: ItemReader가 읽어온 데이터를 원하는 형태로 가공하는 역할. (선택사항)
  • ItemWriter<O>: ItemProcessor가 가공한 데이터 Chunk(묶음)를 한 번에 저장하는 역할.

StepBuilder를 사용하면 이 세 컴포넌트를 아주 간단하게 조립할 수 있습니다.

@Bean
public Step chunkStep(JobRepository jobRepository,
                      PlatformTransactionManager transactionManager,
                      ItemReader<Customer> customerReader,        
                      ItemProcessor<Customer, VipCustomer> customerProcessor,
                      ItemWriter<VipCustomer> vipCustomerWriter) { 

    return new StepBuilder("chunkStep", jobRepository)
            // 1. Input과 Output 타입을 지정하고, Chunk 크기를 10으로 설정
            .<Customer, VipCustomer>chunk(10, transactionManager)
            // 2. Reader, Processor, Writer를 각각 등록
            .reader(customerReader)
            .processor(customerProcessor)
            .writer(vipCustomerWriter)
            .build();
}
  • .<Customer, VipCustomer>chunk(10, ...): 이 Step은 Customer 객체를 입력받아 VipCustomer 객체를 출력하며, 10개의 아이템을 하나의 Chunk로 묶어 처리하겠다는 의미입니다. 이 Chunk 단위로 트랜잭션이 관리되어 안정성도 높아집니다.

Step 주요 설정 및 예외 처리

실제 운영 환경의 배치 Job은 얘기치 못한 예외가 발생해도 최대한 안정적으로 동작해야 합니다. StepBuilder는 Step의 실행 과정을 제어하고 예외 상황에 대처할 수 있는 강력한 기능들을 제공합니다.

재시작 제어

  • startLimit(int)
    • Step의 최대 실행 횟수를 지정합니다.
    • 설정 값을 초과하여 Step이 다시 실행되면 StartLimitExceededException이 발생합니다. 기본값은 Integer.MAX_VALUE입니다.
  • allowStartIfComplete(boolean)
    • Job 재시작 시, 이전에 성공(COMPLETED)했던 Step을 다시 실행할지 여부를 설정합니다.
    • 기본값은 false로, 성공한 Step은 건너뛰지만 true로 설정하면 항상 실행됩니다.

리스너 (Listener)

  • listener(StepExecutionListener)
    • Step의 생명주기에 맞춰 부가적인 로직을 수행하고 싶을 때 사용합니다.
    • Step 실행 전(beforeStep), 실행 후(afterStep) 등 특정 시점에 원하는 로직(예: 로그 기록, 리소스 할당/해제)을 추가할 수 있습니다.

예외처리 (Fault Tolerance)

skip이나 retry 같은 예외 처리 기능은 faultTolerant() 메서드를 먼저 호출해야 활성화됩니다.

  • faultTolerant()
    • Step에 내결함성 기능을 활성화하는 관문 역할을 합니다.
  • skip(Class<? extends Throwable> e)
    • ItemReaderItemProcessor에서 특정 예외가 발생했을 때, 해당 아이템 처리를 건너뛰고 다음 아이템으로 진행하도록 설정합니다.
  • retry(Class<? extends Throwable> e)
    • ItemWriter 등에서 특정 예외 발생 시, Chunk 처리를 즉시 실패시키지 않고 지정된 횟수만큼 재시도하도록 설정합니다.

정리

Step을 구현하는 두 가지 방식을 살펴보았습니다. 어떤 방식을 선택할지는 처리하려는 작업의 성격에 달려 있습니다.

  • 테이블 비우기, 후처리 알림 전송 등 데이터 처리와 무관한 단일 로직이 필요하다면 Tasklet
  • 파일이나 DB의 대용량 데이터를 읽어와 가공하고 저장하는 작업이라면 Chunk 지향 모델이 훨씬 효율적이고 안정적입니다.
profile
왜? 라는 질문이 사라질 때까지

0개의 댓글