본문 바로가기

프로젝트/고민

스프링 배치 적용해보기

문제상황

프로젝트에서 멤버십 기능을 구현하는 중이었는데, 정기 결제를 어떻게 구현해야 할지 고민하게 되었다. 팀원들과 회의를 통해 매달 28일에 결제가 이루어지도록 하기로 결정하고, 첫 결제 시점에서 28일과의 차이만큼 결제를 진행하여 어뷰징을 방지하기로 했다. 또한 구독 취소도 매달 28일까지 진행할 수 있도록 설정했다.

 

이에 따라 모든 구독자에 대한 28일 결제 및 구독 정보 생성 작업이 필요했는데, 이를 배치 작업으로 처리하도록 구현하였다.

 

Spring batch

Spring Batch는 Spring 프레임워크 기반에서 대용량 데이터 처리를 위한 오픈 소스 배치 처리 프레임워크이다.

 

Spring Batch를 사용하면 대용량 데이터 처리를 안정적으로 처리할 수 있다. 대용량 데이터 처리를 위해서는 다수의 데이터를 처리하는 과정에서 발생할 수 있는 오류나 중단에 대한 처리, 병렬 처리, 성능 최적화 등의 문제를 고려해야 하는데, 이러한 문제들을 Spring Batch가 제공하는 다양한 기능들을 활용하여 해결할 수 있다.

 

사용 방법

 

Spring Batch의 동작 과정은 크게 아래와 같이 3단계로 나눌 수 있다.

 

1. Job 설정

  • 배치 처리를 위한 Job을 정의하고, 배치 처리를 위한 Step들을 순서대로 설정한다.
  • Job과 Step을 설정하기 위해서는 BatchConfigurer 인터페이스를 구현하여 설정을 완료해야 한다.

2. Step 설정

  • Step은 Job의 구성 요소로서, 각각의 Step은 배치 처리를 수행한다.
  • Step에서 수행되는 배치 처리는 일정 크기의 데이터를 처리하거나, 특정 조건을 만족하는 데이터를 선택하여 처리할 수 있다.
  • Step은 ItemReader, ItemProcessor, ItemWriter 등의 구성 요소로 구성된다.

3. 배치 처리 실행

  • 배치 처리 실행을 위해서는 JobLauncher 인터페이스를 구현하여 배치 처리를 실행한다.
  • JobLauncher를 통해 Job을 실행하면, Job 내의 Step들이 순서대로 실행되며, 각 Step에서는 ItemReader, ItemProcessor, ItemWriter 등의 구성 요소가 순차적으로 실행된다.
  • 배치 처리 실행 과정에서는 JobExecution, StepExecution 등의 정보를 통해 실행 정보를 확인할 수 있다.

Spring Batch의 각 구성 요소들은 DI(Dependency Injection)를 이용하여 연결되며, DI를 통해 각 요소들의 설정 정보를 외부에서 주입할 수 있다. 이를 통해 Spring Batch는 확장성이 뛰어나고, 유연한 배치 처리 프레임워크로 활용된다.

 

구현

1. Job, Step설정

결제 취소에 대한 배치작업을 통해 구현 방법을 확인해 보자. (빨간줄은 IDE 오류로 생각된다.)

Step은 CHUNK 사이즈 만큼 실행되며 트랜잭션 처리도 CHUNK 단위로 처리된다.

 

먼저 Spring batch에서 제공해주는 JobRepository와 TransactionManager를 주입받아 이름과 함께 설정해준다.

이후 InPut 데이터, OutPut 데이터를 설정해주고 reader를 통해 데이터를 읽어 writer를 통해 db에 저장하는 작업을 수행한다. 아래 taskExecutor는 배치작업을 비동기로 처리해주기 위해 설정하였다.

 

2. Reader 설정

다음으로 ItemReader에 대해 살펴보자

 

이전에 설명했듯이 Reader는 db로 부터 데이터를 읽어오는 작업을 수행한다. 이때 다양한 ItemReader 구현체가 있는데 나는 그중 JpaPagingItemReader를 통해 구현하였다.

 

이 때 PageSize는 Chunk의 크기와는 전혀 무관한 개념이다. PageSize는 Reader에서 한번에 읽어올 데이터의 양을 의미하며 CHUNK_SIZE는 한번에 처리되는 데이터의 양을 말한다. 

 

예를 들어 PageSize가 10, ChunkSize가 5인 경우 10개의 데이터를 읽어와 2개의 Chunk로 분리해 5개씩 데이터를 처리한다.

 

반대로 Chunk가 10, PageSize가 5인 경우 5개의 데이터를 읽어와 설정한 Chunk보다 작은 단위로 처리하게 된다. 즉, PageSize가 Chunk 크기보다 작기 때문에 Chunk 크기보다 작은 크기의 Chunk가 생성된다.

 

3. Writer 설정

Writer에서 Chunk 단위만큼 데이터를 불러와 일괄 처리를 진행해 준다. 나 같은 경우는 Processor를 통해 데이터를 가공할 필요가 없었기에 Procesor 없이 바로 Writer를 사용해 배치처리를 해줬다.

 

4. 배치처리 실행

JobLauncer를 통해 스케쥴러 내에서 해당 시간에 맞는 JobParameters를 설정 해 시간에 맞는 Job을 실행해 줬다.

 

5. 비동기 처리 설정(선택)

Spring Batch에서 비동기 처리를 지원하는 방법은 많지만 나는 taskExecutor를 통해 구현하였다.

해당 방법을 사용할 때 주의할 점은 ItemReader가 멀티 스레드에 안전한지 확인을 해줘야 한다.

내가 사용한 JpaPagingReader는 멀티 스레드에 안전하기에 중복된 데이터를 읽어오지 않는다.

 

다음으로 코드에 대해서 설명하겠다. 배치 작업을 비동기로 처리하기 위한 taskExecutor이다.

 

작업을 수행할 스레드의 개수를 설정하고, 스레드 풀에 저장한다. 그리고 작업을 처리할 때는 설정된 스레드 개수만큼 스레드를 스레드 풀에서 꺼내와 각 스레드에게 데이터를 할당하여 병렬적으로 처리한다. 이렇게 함으로써 각 스레드는 할당된 데이터를 동시에 처리하고, 전체 작업을 빠르게 완료할 수 있다.

 

이 때 예외 발생시 롤백 단위는 ChunkSize이며 처리의 순서와 관계 없이 해당 Chunk에 포함된 데이터에 대하여 모두 롤백이 일어난다.

 

성능 차이 기존 동기적 코드

 

비동기적 코드

 

약 2배 향상한 것을 확인할 수 있는데, pc 사양에 맞게 스레드를 조절해서 사용하면 아마 더 큰 퍼포먼스가 나올 것으로 예상된다.

 

+ 배치는 보통 서버를 따로 두는데 현재 나의 여건 상 배치 서버를 따로 두기에는 무리가 있어 같은 프로젝트에 작업했다.

 

 

 

마치며

Spring Batch와 스케줄러를 이용하여 배치작업을 처음으로 처리해보았다. Batch의 버전이 5.0으로 업데이트되면서 기존에 사용했던 방법들이 모두 deprecated 되어 자료가 부족했기 때문에 구현하는 과정에서 어려움을 겪었다. 그러나 끈기를 갖고 공식 문서와 내부 코드를 탐구하여 구현에 성공했고, 이를 통해 성취감을 느끼며 한 걸음 더 성장한 것 같다.