arrow_upward
본문 바로가기
Spring

[Spring Batch] Chunk 지향 처리

by dawncode 2024. 7. 19.

 

이전까지는 Step을 처리할 때 Tasklet으로 단일 작업을 처리했었다. 간단한 처리를 할 때는 Tasklet만 써도 상관없지만 대량의 데이터를 처리할 때 수십 수백만건의 데이터를 한 번에 처리하는 것은 그렇게 용이하지는 않다.

이번에는 스프링 배치에서 데이터를 처리할 때 많은 이점을 제공하는 Chunk에 대해서 알아볼 것이다.

 

프로젝트 설정은 이전 포스팅과 동일하다.

 

 

Chunk란?

스프링 배치는 일반적으로 Chunk 지향적인 처리 스타일을 사용한다. Chunk는 대량의 데이터를 효율적으로 처리할 수 있으며 데이터를 일정한 크기(청크)로 나누어 처리하는 것을 의미한다.

 

 

Chunk는 다음 요소들로 구성된다.

  • ItemReader: 데이터를 읽어온다. (FILE, DB)
  • ItemProcessor: 읽어온 데이터를 처리하는 역할을 한다. 데이터 변환, 검증, 필터링 등의 작업을 수행한다.
  • ItemWriter: 읽어온 데이터를 저장하는 역할을 한다.

ItemReader와 ItemWriter는 청크를 구성할 때 필수 요소이고, ItemProcessor는 처리(변환)할 데이터가 없으면 구현하지 않아도 된다.

ItemReader는 DB 뿐만 아니라 CSV, XML, JSON 등의 다양한 리소스를 읽을 수 있다.

 

Chunk는 Chunk Size크기만큼 데이터를 읽어와서 Chunk 단위로 트랜잭션을 처리한다.

예를 들어, 30건의 데이터를 Chunk Size를 10으로 처리한다면 3번에 나누어서 처리하기 때문에 트랜잭션이 3번 커밋되고 중간에 처리가 실패하면 Chunk 만큼만 롤백된다.

이로 인해 대량의 데이터를 처리할 때 트랜잭션 관리에 유용하며, 오류 발생 시 해당 Chunk부터 다시 처리할 수 있기 때문에 복구가 쉬워진다.

 

우선 청크를 처리하기 위한 ItemReader, ItemProcessor, ItemWriter 인터페이스를 확인해본다.

public interface ItemReader<T> {
    @Nullable
    T read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException;
}

public interface ItemProcessor<I, O> {
    @Nullable
    O process(@NonNull I var1) throws Exception;
}

public interface ItemWriter<T> {
    void write(List<? extends T> var1) throws Exception;
}

 

인터페이스 모두 하나의 메서드만을 갖는 @FunctionalInterface 이므로 구현 시 람다를 사용하면 보다 간결하게 코드를 작성할 수 있다.

 

 

Chunk 사용

 

청크에서 제네릭 타입에 들어가는 <String, String>은 ItemReader에서 읽어들일 입력 타입과 ItemProcessor가 처리해서 ItemWriter로 출력할 타입을 지정한다. 그리고 Chunk Size를 지정해주면 된다. 

 

입력 데이터를 출력할 때 그대로 사용한다면 Input과 Output 타입을 같도록 하면 되고, 처리과정이 따로 필요없다면 ItemProcessor는 생략(주석처리)해도 된다.

 

 

 

예제 코드에서는 간단하게 Size가 10인 List를 생성해서 아이템을 생성했고 Chunk Size가 5이므로 데이터를 두 번 나눠서 처리할 것이다.

 

  • ItemReader에서 Chunk Size만큼 데이터를 읽어온다. (DB의 페이징 처리 시 Page Size만큼 읽어온다.)
  • ItemProcessor에서 읽은 데이터를 하나씩 처리(변환)하는 작업을 한다.
  • ItemWriter에서 데이터를 출력한다. 예제는 간단히 로그로 출력했지만 DB에 Write하는 작업을 한다.

ItemReader에서 데이터를 Chunk Size(실제 DB 페이징 처리 시 Page Size)만큼 읽어온 뒤 ItemProcessor에서 한 건씩 데이터를 처리하고 ItemWriter에서 출력하는 작업을 하고있다. 

 

예제에서는 간단히 작성했지만 ItemReader와 ItemWriter의 종류는 매우 다양하다. 이 부분은 이후 ItemReader와 ItemWriter를 포스팅할 일이 있으면 좀 더 자세히 작성할 것이다.

 

 

Chunk Size와 Page Size

ItemReader에서 Chunk Size만큼 데이터를 읽어온다고 했는데 RepositoryItemReader, JdbcPagingItemReader, JpaPagingItemReader 등을 사용하면 페이징 처리시 Page Size를 지정하게 된다.

Chunk Size와 Page Size는 다른 개념으로 Chunk Size는 스프링 배치에서 데이터를 처리할 크기이고, Page Size는 DB에서 읽어들일 데이터의 레코드 수이다.

 

Chunk Size를 20으로 잡았는데 Page Size를 5로 지정하면 하나의 청크를 처리할 때마다 4번의 Select 쿼리가 발생하게 된다. 이는 한 번 트랜잭션을 처리하는데 4번의 조회가 발생해서 성능상의 이슈가 발생한다. 이로 인해 Chunk Size와 Page Size는 크기를 맞춰서 처리해주는 것이 좋다.

 

 

Chunk의 다양한 기능

 

그 밖에도 Chunk 지향 처리를 할 때 메서드 체이닝으로 다양한 기능을 사용가능하다. 여기서는 작성된 코드 외에 몇가지만 설명할 것이다.

  • faultTolerant(): 예외가 발생했을 때 재시도가 가능하도록 한다.
  • retryLimit(): 재시도의 횟수를 설정한다.
  • retry(): 해당 예외가 발생했을 때 재시도한다.
  • listener(): Step Event Listener를 지정한다.
  • transactionManager(): 트랜잭션 매니저를 설정한다.
  • taskExecutor(): TaskExecutor를 지정해서 멀티스레딩을 구현할 때 사용한다.

이 외에도 많은 기능들이 있지만, 그 기능들을 사용할 때 알아가는 것이 좋을 것 같다.