카테고리 없음

[Spring Batch] 배치의 두 가지 심장: Tasklet vs Chunk 지향 처리

나는 나야 2026. 2. 15. 11:07

이번에는 Spring Batch의 Step을 구성하는 두 가지 핵심 처리 모델, Tasklet(태스크릿)Chunk(청크)에 대해 심층 분석해 보시죠.

1. 태스크릿(Tasklet) 지향 처리: 단순함의 미학

태스크릿(Tasklet) 모델은 Spring Batch에서 가장 기본적인 Step 구현 방식입니다. 복잡한 데이터 처리보다는 단순한 시스템 작업이나 유틸성 작업에 주로 사용됩니다.

언제 사용하는가?

대량의 데이터를 읽고 쓰는 ETL 작업이 아닌 경우, 대부분 Tasklet을 사용합니다.

  • 로그 파일 삭제
  • 단순 알림(이메일, 슬랙) 발송
  • 외부 API 호출 후 단순 로깅
  • 오래된 파일 아카이빙

구현 핵심: Tasklet 인터페이스

Tasklet 인터페이스의 execute() 메서드에 로직을 작성하면 됩니다.

@FunctionalInterface
public interface Tasklet {
    @Nullable
    RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception;
}

 

프로세스 제거 예제 (feat. RepeatStatus)

Tasklet의 핵심은 RepeatStatus 반환값에 있습니다.

@Slf4j
public class ZombieProcessCleanupTasklet implements Tasklet {
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        log.info("💀 시스템 안정화 완료.");
        
        return RepeatStatus.FINISHED; // 작업 끝! Step 종료
        // return RepeatStatus.CONTINUABLE; // 아직 할 일이 남음 (반복 실행)
    }
}

 

  • RepeatStatus.FINISHED: "다 끝났다." Step을 종료하고 다음으로 넘어갑니다.
  • RepeatStatus.CONTINUABLE: "아직이다." execute() 메서드를 다시 호출합니다.
    • 왜 while문을 안 쓰고 CONTINUABLE을 쓸까요?
    • 바로 트랜잭션 때문입니다. execute() 호출 한 번당 하나의 트랜잭션이 생성됩니다. 작업을 쪼개서 실행하여 중간에 실패하더라도 전체가 롤백되는 대참사를 막기 위함입니다.

Configuration 설정 (Tip: ResourcelessTransactionManager)

DB 접근이 없는 단순 파일 작업이나 알림 발송의 경우, 굳이 무거운 DB 트랜잭션을 쓸 필요가 없습니다. 이때는 ResourcelessTransactionManager를 사용하면 효율적입니다.

@Bean
public Step zombieCleanupStep() {
    return new StepBuilder("zombieCleanupStep", jobRepository)
            .tasklet(zombieProcessCleanupTasklet(), new ResourcelessTransactionManager()) // DB 트랜잭션 생략
            .build();
}

2. 청크(Chunk) 지향 처리: 대용량 데이터의 구원자

배치의 꽃, 청크(Chunk) 지향 처리입니다. 데이터 기반의 읽기(Read) → 처리(Process) → 쓰기(Write) 패턴을 다룹니다.

왜 'Chunk(덩어리)'인가?

100만 건의 데이터를 한 번에 메모리에 올리면 어떻게 될까요?

👉 OOM(Out Of Memory) 발생 & 시스템 폭발 💥

Spring Batch는 이를 방지하기 위해 데이터를 지정한 크기(Chunk Size)만큼 쪼개서 처리합니다.

  • 메모리 보호: 한 번에 10개(Chunk Size)씩만 메모리에 올립니다.
  • 트랜잭션 관리: 100만 건을 하나의 트랜잭션으로 묶는 건 자살행위입니다. Chunk 단위로 트랜잭션을 커밋하여 실패 시 해당 Chunk만 롤백합니다.

청크 처리의 3대장 (The Trio)

  1. ItemReader (읽기): 데이터를 하나씩 읽어옵니다. (read())
    • 더 이상 읽을 데이터가 없으면 null을 반환하며 Step이 종료됩니다.
  2. ItemProcessor (처리): 읽어온 데이터를 하나씩 가공합니다. (process())
    • 필터링, 데이터 변환, 검증을 수행합니다. (생략 가능)
  3. ItemWriter (쓰기): 가공된 데이터를 **Chunk 단위(List)**로 모아서 한 번에 씁니다. (write())
    • Reader/Processor와 달리 뭉텅이로 처리한다는 점이 핵심입니다.

주의! 실제 동작 흐름 

많은 개발자가 헷갈려 하는 부분입니다.

Chunk Size가 10일 때의 흐름

  1. ItemReader가 데이터를 1개 읽습니다.
  2. ItemReader가 10번 호출될 때까지 반복하여 Chunk(입력)를 채웁니다. (X) -> (수정: Reader가 읽고 바로 Processor로 가는 게 아닙니다)

정확한 흐름:

  1. Read: ItemReader.read()를 호출해 아이템을 하나 읽습니다.
  2. Process: 읽은 아이템을 바로 ItemProcessor.process()로 가공합니다.
  3. Repeat: 위 1-2 과정을 Chunk Size(10번)만큼 반복하여 가공된 결과물 리스트(List)를 만듭니다.
  4. Write: 10개가 모인 리스트를 ItemWriter.write()에 전달하여 한 번에 씁니다. (Transaction Commit)

코드 구성 예시

@Bean
public Step processStep(JobRepository jobRepository, PlatformTransactionManager tm) {
   return new StepBuilder("processStep", jobRepository)
           .<Customer, CustomerSummary>chunk(10, tm) // 10개씩 끊어서 처리
           .reader(itemReader())       
           .processor(itemProcessor()) 
           .writer(itemWriter())      
           .build();
}
  • <Input, Output>: Reader가 읽는 타입, Writer가 쓰는 타입을 제네릭으로 명시합니다.
  • chunk(10): 10개 단위로 트랜잭션을 커밋하겠다는 의미입니다.

3. 요약: Tasklet vs Chunk 선택 가이드

구분 태스크릿(Tasklet) 청크(Chunk)
주요 목적 단순 작업, 유틸리티, 알림 대용량 데이터 처리 (ETL)
처리 방식 단일 메서드 (execute) 실행 Read → Process → Write 반복
트랜잭션 execute 전체가 1 트랜잭션 (기본) Chunk 단위로 트랜잭션 분리
구현 난이도 쉬움 설정할 것이 많음 (3대장 구현)
비고 RepeatStatus로 반복 제어 Chunk Size로 메모리/트랜잭션 제어