본문 바로가기
Spring/Batch

스프링 배치 스텝(Step)의 두 가지 유형: Tasklet vs Chunk 지향 처리

by coding_whale 2026. 5. 7.
반응형

스프링 배치(Spring Batch)에서 Step은 Job을 구성하는 실질적인 독립 실행 단위다. Job이 전체적인 시나리오를 담고 있다면, Step은 그 시나리오 안에서 실제로 무엇을 할지를 정의한다. 스프링 배치는 작업의 성격과 데이터의 규모에 따라 두 가지 처리 모델을 제공한다.

  1. 태스크릿 지향 처리 (Tasklet-Oriented Processing)
  2. 청크 지향 처리 (Chunk-Oriented Processing)

이 두 모델은 단순히 구현 방식의 차이를 넘어, 시스템 자원을 관리하고 트랜잭션을 처리하는 철학 자체가 다르다. 오늘은 이 두 모델의 특징과 동작 원리를 심층적으로 분석해 본다.


1. 태스크릿(Tasklet) 지향 Step: 단일 작업의 효율적인 처리

태스크릿 지향 Step은 스프링 배치에서 가장 단순한 구조를 가진다. 주로 복잡한 데이터 흐름 없이 단일 비즈니스 로직을 실행할 때 사용하며, 읽기-처리-쓰기라는 정형화된 패턴이 필요하지 않은 작업에 적합하다.

🔍 주요 활용 상황

태스크릿은 대량 데이터를 다루기보다 다음과 같은 시스템 관리나 유틸성 작업을 수행할 때 주로 사용한다.

  • 파일 관리: 배치 시작 전 임시 디렉토리 청소, 작업 완료 후 로그 파일 아카이빙 등
  • 알림 발송: 특정 조건 충족 시 담당자에게 이메일이나 메시지 전송
  • 단순 DB 조작: 특정 테이블의 데이터를 모두 삭제(Truncate)하거나 간단한 상태값 업데이트
  • 외부 API 호출: 단발성 API 요청을 보내고 결과를 로깅하는 작업

 

💻 구현 방식과 RepeatStatus

Tasklet 인터페이스의 execute() 메서드에 실행하고자 하는 로직을 구현한다. 이때 핵심은 반환값인 RepeatStatus다.

@Slf4j
public class SimpleFileCleanupTasklet implements Tasklet {

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
        log.info("배치 작업 전 임시 파일을 삭제합니다.");
        
        // 파일 삭제 로직 수행...
        
        // FINISHED를 반환하면 해당 Step이 완료되었음을 스프링 배치에 알린다.
        return RepeatStatus.FINISHED;
    }
}
  • RepeatStatus.FINISHED: Step의 처리가 완료되었음을 의미하며 다음 단계로 진행한다.
  • RepeatStatus.CONTINUABLE: 특정 조건이 만족될 때까지 execute() 메서드를 반복해서 호출한다.

 

⚙️ 트랜잭션 처리의 특징

태스크릿 모델에서 트랜잭션 매니저를 설정하면, 매번 execute()가 실행될 때마다 새로운 트랜잭션이 시작되고 종료(커밋)된다. 만약 로직 내부에서 100만 건을 반복문으로 처리하다 에러가 발생하면 전체가 롤백되지만, CONTINUABLE을 활용해 execute()를 여러 번 호출하게 설계하면 각 호출 단위로 트랜잭션이 분리되어 데이터 손실 위험을 줄일 수 있다.

 

 

2. 청크(Chunk) 지향 Step: 대규모 데이터 처리를 위한 표준 모델

청크 지향 Step은 스프링 배치가 제공하는 가장 강력한 기능 중 하나다. 데이터를 일정한 덩어리(Chunk) 단위로 나누어 처리하는 방식이며, 대량의 데이터를 다루는 ETL(Extract, Transform, Load) 작업의 표준이다.

🔍 왜 '청크' 단위로 처리하는가?

수백만 건의 데이터를 한 번에 메모리에 올리고 처리하는 것은 매우 위험하다. 청크 지향 처리는 이를 해결하기 위해 두 가지 핵심 전략을 사용한다.

  1. 메모리 효율성: 설정한 청크 사이즈만큼만 데이터를 읽어오기 때문에 메모리 사용량이 일정하게 유지된다.
  2. 트랜잭션의 안정성: 청크 단위로 트랜잭션을 커밋한다. 작업 도중 에러가 발생하면 해당 청크만 롤백하면 되므로, 처음부터 다시 시작할 필요가 없어 복구 속도가 빠르다.

 

🛠️ 청크 처리를 지탱하는 3대 컴포넌트

  • ItemReader: 데이터 소스(DB, 파일 등)에서 데이터를 하나씩 읽어오는 역할을 한다.
  • ItemProcessor: 읽어온 데이터를 변환하거나 필터링한다. 비즈니스 로직이 들어가는 곳이며 생략 가능하다.
  • ItemWriter: 처리된 데이터를 청크 단위로 묶어서 최종 목적지에 기록한다.

 

💻 청크 Step 구성 예시

StepBuilder를 통해 각 컴포넌트를 연결하고 청크 크기를 지정한다.

@Bean
public Step chunkProcessingStep(
        JobRepository jobRepository,
        PlatformTransactionManager transactionManager,
        ItemReader<InputDto> reader,
        ItemProcessor<InputDto, OutputDto> processor,
        ItemWriter<OutputDto> writer) {
    return new StepBuilder("chunkStep", jobRepository)
            .<InputDto, OutputDto>chunk(100, transactionManager) // 청크 크기를 100으로 설정
            .reader(reader)
            .processor(processor)
            .writer(writer)
            .build();
}

 

 

3. 실행의 끝과 반복의 종료 조건

청크 지향 Step은 데이터가 없을 때까지 위 과정을 반복한다. 그렇다면 스프링 배치는 언제 '데이터가 없다'고 판단할까?

정답은 ItemReader.read()가 null을 반환할 때다. Reader가 데이터를 더 이상 가져올 수 없어 null을 넘겨주면, 배치는 이번 청크가 마지막임을 인식하고 남은 아이템들을 Processor와 Writer로 보낸 후 Step을 종료한다.

 

4. 트레이드오프: 적절한 청크 사이즈 결정하기

청크 사이즈는 성능과 안정성 사이의 균형을 맞추는 핵심 설정이다.

  • 사이즈가 큰 경우: I/O 횟수가 줄어들어 전체적인 처리 속도가 빨라지지만, 메모리 부담이 커지고 오류 발생 시 롤백 범위가 넓어진다.
  • 사이즈가 작은 경우: 메모리 사용량이 매우 적고 오류 복구가 정밀해지지만, 트랜잭션 커밋과 I/O가 잦아져 전체 실행 시간이 길어진다.

일반적으로는 서버 사양과 데이터의 복잡도에 따라 500~2000 사이의 값을 많이 사용하며, 성능 테스트를 통해 최적의 값을 찾아가는 과정이 필요하다.

 

5. 한눈에 보는 비교 테이블

구분태스크릿(Tasklet)청크(Chunk)

적합한 상황 단발성 작업, 유틸성 로직 대량 데이터 수집 및 가공
구현 방식 하나의 인터페이스 구현 Reader, Processor, Writer 조합
트랜잭션 범위 execute() 메서드 단위 지정한 청크 사이즈 단위
주요 장점 로직 구성이 자유롭고 단순함 메모리 관리 및 대용량 처리 안정성

 

6. 마무리하며

단순히 "무엇을 실행할 것인가"가 중요하다면 Tasklet을, "많은 양의 데이터를 어떻게 안전하게 옮길 것인가"가 중요하다면 Chunk를 선택하는 것이 기본 전략이다. 이 두 가지 도구를 적재적소에 적용하는 것이 중요하다.

반응형