포스트

여러 작업을 한 번에 처리해야 한다면? 배치와 스프링 배치 이해

배치 작업에 대한 개념과 스프링 배치에 대한 전반적인 개념에 대해 다룹니다.

여러 작업을 한 번에 처리해야 한다면? 배치와 스프링 배치 이해

들어가며

최근 진행중인 사이드 프로젝트에서 배치 작업 기능을 구현해야하는 일이 생겼습니다. 배치에 대한 개념들과 이를 구현하기 위해 조사하고 적용했던 내용들을 한번 정리해봤습니다. 배치 작업의 정의, 배치작업의 특성, 그리고 배치처리를 위한 프레임워크인 스프링 배치에 구조에 대해서 다뤄볼 예정입니다.

필요했던 일은?

현재 진행중인 프로젝트는 크게 2개의 시스템으로 구성되어 있고 이 중 배치 작업을 필요로 했던 서비스는 추천 시스템입니다. 추천 시스템은 피드 시스템으로부터 유저의 관심 정보를 요청으로 받아 유저가 관심있어 할 만한 개인화된 피드 리스트를 반환해주는 역할을 담당하고 있습니다.

먼저 현재 구현중인 추천 시스템은 유저와 비슷한 관심사를 가진 유저들이 좋아하는 피드들을 추천해주는 일종의 그룹 기반 협업 필터링 방식을 채택하고 있습니다. 예를 들면, 내가 속한 직군의 사람들이 좋아하는 피드들을 선별하고 추천해주는 방식입니다. 이 방식은 과거 사용자의 행동 데이터와 데이터 중심의 추천방식으로 알고리즘이 직관적이고, 아이템의 도메인적인 지식이 필요하지 않다는 장점이 있습니다. 하지만 이 방식은 사용자 수와 사용자가 생성하는 게시글 수가 증가할수록 추천 피드를 선별하는 데 시간이 많이 소요될 수 있다는 단점이 있습니다.

현재 구조는 추천 시스템이 피드 시스템으로부터 사용자 정보를 넘겨받아 추천 연산을 수행한 후 응답하는 흐름입니다. 시스템 규모가 커질수록 추천 연산 시간이 점차 늘어나면 추천 시스템의 응답속도도 자연스레 느려질 수 밖에 없습니다. 추천 시스템의 응답 지연은 이를 호출하는 피드 시스템에도 영향을 줄 것이고 결국 전체 서비스의 응답 속도 저하와 사용자의 경험 저하로 이어지게 될 가능성이 높습니다. 따라서 시스템의 규모가 커지더라도, 추천 기능은 일정한 응답속도를 보장하는 것이 필요했습니다.

이를 해결하기 위해 추천 시스템 내 저장되어 있는 추천에 필요한 피쳐 정보들을 통계 정보를 일정 주기마다 미리 계산하여 캐싱해놓는 구조를 설계했습니다. 이 방식이라면 시스템 규모가 커져도 안정적인 응답 속도를 유지할 수 있을 것으로 기대했습니다.

이러한 배경에서, 추천에 사용되는 피처들의 통계 정보를 주기적으로 미리 계산하는 작업을 배치로 처리하기로 결정했습니다. 다음 순서로 배치가 무엇인지 간단히 살펴보고, 프로젝트에 적용한 Spring Batch의 구조에 대해 다뤄보려 합니다.

(*현재 프로젝트에서는 추천 알고리즘보다 백엔드 구조 설계에 중점을 두고 구현하는 데 집중했습니다. 추천과 관련해 잘못된 부분이나 오해가 있다면 언제든지 알려주시면 감사하겠습니다.)

배치란?

배치는 특정 시점에 여러 작업을 일정한 단위로 묶어 한꺼번에 처리하는 방식을 의미합니다. 배치 프로그램은 정해진 시간에 일괄적으로 작업을 처리하는 프로그램을 말합니다. 실시간으로 작업을 처리하는 것이 아닌, 정해진 시간에 몰아서 처리하는 특성을 갖습니다.

서비스를 운영하는 관점에서는 요청에 따라 지금 당장 처리될 필요는 없지만, 주기적으로 처리해주어야하는 일들이 배치성 작업에 속하는데 주로 필요한 데이터를 모아서 한꺼번에 처리해야하거나, 지연처리가 필요할 때, 시간이 오래 걸리거나 대량의 데이터를 처리해야하는 상황들이 있습니다. 덕분에 자원을 효율적으로 사용할 수 있다는 장점도 존재합니다.

예를 들면 월말에 기존에 발생했던 내역들을 종합해서 월별 거래 명세서를 만든다거나 매일 자정에 사용자 포인트를 정산한다거나, 누락된 데이터들을 주기적으로 검증하거나 보증해야하는 경우가 예시들이 될 수 있을 것 같습니다.

배치작업의 특징

데이터 처리 방식을 크게 실시간 처리와 배치 처리로 나누어서 배치 작업의 특성을 살펴봤습니다.

배치 처리

  • 일정한 시점에 데이터를 모아서 일괄 처리하는 방식
  • 대량의 데이터에 대해 동일한 작업을 수행할 때 적합
  • 비동기적으로 처리되며, 처리 결과를 바로 확인하지 않아도 되는 경우에 사용
  • 효율적인 자원 활용이 가능하며, 필요할 때만 서버를 가동하므로 비용 절감 효과가 있음
  • 처리 시점을 조정할 수 있어 시스템 부하를 피크 타임 외로 분산 가능
  • 작업 스케줄링과 충돌 관리가 필요하므로 운영이 복잡해질 수 있음
  • 데이터의 일관성을 보장하기 용이함

실시간 처리

  • 데이터가 발생하는 즉시 처리하는 방식
  • 사용자에게 즉각적인 응답이나 피드백이 필요한 경우 사용
  • 높은 성능과 낮은 지연시간을 요구하므로 상대적으로 비용이 많이 듦
  • 피크 타임에 처리량이 급증할 경우 시스템 성능 저하가 발생할 수 있음

배치 처리의 가장 큰 장점은 시스템 자원을 효율적으로 사용할 수 있다는 점이며, 실시간 응답이 필요하지 않은 작업에 적합합니다. 반면, 실시간 처리는 즉각적인 반응이 필요한 상황에서 필수적이며, 성능과 비용 측면에서 배치 처리보다 부담이 큽니다. 그래서 해당 작업이 즉각적인 응답을 요구하는 실시간성 특성을 갖는지 아닌지를 파악해보는 것이 작업을 처리하는 방법의 기준이 될 수 있습니다.

배치 예시

배치를 사용하는 예시들은 다음과 같은 상황에서 주로 사용될 수 있습니다.

서비스 분야에서의 배치 처리

  1. 메시지, 이메일, 푸시 등을 발송할 때,
  2. 데이터를 마이그레이션할 때,
  3. 실패한 트랜잭션을 재처리할 때,
  4. 쿠폰, 포인트 등이 만료되었을 때 소진시키는 처리를 할 때,
  5. 월말 또는 월초에 특정 데이터를 생성할 때, 월별 거래 명세서

데이터 분야에서의 배치 처리

  1. 각 서비스의 데이터를 데이터 웨어하우스에 저장할 때, (ETL, Extract-Transform-Load)
  2. 아마존에서 연관 상품을 추천하는 데이터 모델을 만들 때,
  3. 유저의 리텐션 등 마케팅에 참고할 데이터 지표를 집계할 때

Spring Batch란 ?

대규모 데이터를 일괄적으로 처리하는데 최적화된 자바 기반의 배치 프레임워크입니다. 견고한 배치 애플리케이션 개발이 가능하도록 다양한 기능을 가지고 있는 것이 장점입니다.

스프링 배치 프레임워크는 스프링 프레임워크의 일부로 스프링의 트랜잭션 관리 & 데이터 엑세스 기술을 확장해 배치 전용 기술들을 제공한다는 점이 장점입니다.

또한 스프링의 서비스에서 작성했던 코드들을 재활용해서 배치처리에 사용이 가능하기 때문에 기존 스프링 애플리케이션과의 뛰어난 통합성을 제공한다는 특징을 갖습니다. 기존 모듈을 스프링 배치에서 Import 해서 사용할 수도 있다고 합니다. 현제 제가 만들고 있는 프로젝트 역시 스프링 프레임워크 기반으로 만들어졌고, 이에 대한 생산성 측면에서도 장점이 있다고 판단하여 Spring Batch를 도입해보는 것으로 결정했습니다.

이 외에도 대용량 데이터 배치처리에 필요한 다양한 기능들을 제공합니다.

  • 로깅과 추적, 트랜잭션 관리, 작업 처리 통계.
  • 청크 기반 작업 처리.
  • 실패 복구, 재시작/건너뛰기 가능.
  • 스케줄링 연동 용이.
  • 다양한 입출력 (Reader, Writer) 지원.
  • 멀티 코어, 또는 멀티 서버에서 처리 분산 기능.

스프링 배치 아키텍처

Spring Batch Layer스프링 배치

스프링 배치는 다양한 사용자와 확장성을 고려 설계하는 과정에서 위와 같은 아키텍처를 채택했습니다. 사용자가 배치 처리에서 필요로 하는 다양한 요구 사항들은 각 계층에서 적절하게 분리되어 처리됩니다. 각 계층을 간단하게만 설명해보면 다음과 같습니다.

먼저 Application과 Batch Core 두 레이어가 의존하고 있는 Batch Infrastructure 레이어는 공통 데이터 읽기/쓰기, 재시도 템플릿과 같은 공통 기능들을 제공합니다.

Batch Core 레이어는 스프링 배치가 배치처리를 위해 제공하는 핵심 추상 클래스 및 템플릿 클래스들을 제공합니다. 이 레이어는 배치 작업을 실행하고, 제어하는 역할을 담당합니다.

Application 레이어는 사용자에 의해 실제 배치 작업들의 흐름을 정의하는 역할을 담당합니다. 사용자가 작성하는 실제 작업이 정의되는 구현체들이 이 레이어에 속하게 됩니다.

스프링 배치 구성요소

앞선 아키텍처에 이어서 스프링 배치를 이루고 있는 구성 요소들도 한번 짚고 넘어가보려고 합니다. 앞의 Core와 Infra에 대응되는 색상으로 다음 스프링 배치 구성 요소들을 살펴보면 좋을 것 같습니다.

batch-diagram스프링 배치 구성 요소

먼저 배치 처리할 작업을 Job이라고 부릅니다. 이 Job을 실행시키는 역할을 하는 컴포넌트가 JobLauncher이고 일종의 트리거라고 볼 수 있습니다.

Step은 Job을 처리하기 위한 단계들을 의미합니다. 작업을 처리하기 위한 단계는 여러 단계가 존재할 수 있으므로 1:N 관계를 갖을 수 있습니다. 즉, 하나의 Job이 여러 Step을 가질 수 있음을 의미합니다.

예를 들어 “회원 포인트 정산”이라고 하는 배치 작업에 비유를 해보면,

  • “회원 포인트 정산” Job이 되고,
  • “회원 정보 읽기 → 포인트 계산 → DB 저장” 와 같은 세부 단계가 Step 들이 됩니다.

마지막으로 JobRepository 는 Job, Job 실행 이력, 파라미터, Step 정보 등을 저장합니다. 이러한 정보들은 Job이 중단되었을 때 재시작을 하거나, 혹은 중복 실행을 방지하기 위한 기능들을 위해 별도의 DB에 저장이 됩니다.

앞서 설명한 주요 요소들에 대해서 개별적으로 자세하게 정리해보겠습니다.

Job

JobJob

위 그림은 Job의 계층구조를 표현한 그림입니다.

Job전체 배치 프로세스를 캡슐화한 도메인을 의미합니다. 논리적인 배치 처리 단위인 셈이죠. 비유하자면 객체를 만들기 위한 클래스-like에 비유해볼 수 있을 것 같습니다. Job에는 어떤 Step들을 어떤 순서로 실행할지가 정의되어 있습니다.

1
2
3
4
5
6
7
8
@Bean
public Job footballJob(JobRepository jobRepository) {
    return new JobBuilder("footballJob", jobRepository)
                     .start(playerLoad())
                     .next(gameLoad())
                     .next(playerSummarization())
                     .build();
}

JobInstance는 Job과 JobParameter의 조합으로 이루어진 실질적인 실행 단위가 됩니다. JobParameter는 Job 실행 시 입력되는 외부 파라미터로 예를 들어 날짜, 사용자 ID 등이 해당됩니다.

동일한 Job이더라도, JobParameter가 다르다면 이는 다른 JobInstance로 식별되는 것이죠. 예를 들면 “회원 포인트 정산Job” + “오늘날짜, parameter”의 조합이 유일한 JobInstance가 됩니다.

JobExecution은 특정 JobInstance의 실행 기록을 의미합니다. 실패/재시도할 때마다 새로운 JobExecution이 생성된다고 볼 수 있습니다. 상태: STARTING, STARTED, FAILED, COMPLETED, STOPPED

Step

StepStep

Step은 Job을 수행하기 위한 세부 단계, 작업 처리의 단위입니다. 전체 Job이 수행되기 위해서 실행되는 세부 처리 단위를 의미하는데, 각 Step은 처리해야할 아이템을 읽어들이는 Reader, 아이템을 처리하는 Processor, 처리한 아이템을 다시 쓰는 Writer로 구성될 수 있습니다.

Step은 방식에 따라 대표적으로 Chunk 기반과 Tasklet 기반인 2가지 종류의 Step이 존재합니다.

Chunk-Step

Chunk 기반 Step Chunk 기반 스텝은 아이템을 하나의 트랜잭션 안에서 일정 단위(Chuck)만큼 아이템을 처리하는 방식을 말합니다. 이 과정에서 총 3개의 컴포넌트들이 조립되어서 Step을 이룹니다. 아이템을 읽거나/조회하는 ItemReader, 아이템을 가공하는 ItemProcessor(선택 사항), 처리한 아이템을 쓰는 ItemWriter로 구성됩니다.

Chunk는 트랜잭션에서 처리되어야 하는 아이템의 개수를 의미합니다.

Chunk 기반 Step의 동작과정은 “읽고-처리하고-쓰고”의 일련의 과정으로 트랜잭션이 수행될 것 같지만 그렇지 않습니다. 데이터를 “읽어오고 - 처리하고”의 작업을 반복하다가 처리가 완료된 데이터의 개수가 Chunk 사이즈에 도달하면 그때 Write를 수행합니다.

이때 “읽어오는” 아이템을 개수를 Commit Inverval이라고 부릅니다. 아이템을 조회하는 단위를 의미를 하는데 보통은 이 Commit inverval과 Chunk 사이즈를 동일하게 가져가거나, Commit inverval <= Chunk 만큼 설정한다고 합니다. 이는 롤백시 어느 아이템까지 조회와 커밋이 되었는지 추적하기 어려운 점도 있고, 혼동을 막는 이유도 있고, 실제로 불필요한 쿼리 횟수가 증가하기 때문에 그렇다고 합니다.

또 하나 주목해야할 점은 이 Step의 과정이 트랜잭션 단위로 실행된다는 점입니다. 만일 작업을 수행하다 중간에 실패해서 트랜잭션이 롤백된다면, Chunk 단위로 롤백이 됩니다. 만일 Chunk가 50개이고, 1번 데이터부터 불러와서 처리하다가 33번째 데이터에서 실패한다면, 1~33번까지도 모두 처리했던 결과가 반영되지 않습니다.

Tasklet-Step

Tasklet 기반 Step은 앞선 Chunk 기반과는 다르게 조금 더 가벼운 배치처리를 위한 Step입니다. Chunk처럼 아이템들에 반복처리를 해야할 필요성이 없을때 사용을 합니다.

예를 들면, 배치 작업을 수행하기 전에 파일을 삭제한다던가, 테이블을 삭제한다던가 와 같은 단순한 처리들에 적합하게 사용될 수 있습니다. 그래서 별도의 Reader, Processor, Writer가 존재하지는 않고, 하나의 트랜잭션 안에서 작업이 수행이 됩니다.

JobRepository

JobRepository는 Job, Job 실행 이력, 파라미터, Step 정보 등을 저장하는 컴포넌트 입니다. 배치를 실행하고 관리하기 위한 메타 데이터들을 저장합니다. 이 정보들을 바탕으로, 배치의 수행 결과를 추적하고 재 실행할 수도 있게 도와줍니다.

  • BATCH_JOB_INSTANCE : Job 인스턴스 정보
  • BATCH_JOB_EXECUTION : Job 실행 정보 (시작, 종료 시간 등)
  • BATCH_JOB_EXECUTION_PARAMS : Job 실행 시 전달된 파라미터
  • BATCH_JOB_EXECUTION_CONTEXT : Job 실행 컨텍스트 데이터
  • BATCH_STEP_EXECUTION : Step 실행 정보
  • BATCH_STEP_EXECUTION_CONTEXT : Step 실행 컨텍스트 데이터

이 정보들은 실제 Spring Batch 애플리케이션과 연결된 데이터베이스에 테이블 형태로 기록이 됩니다. 주로 배치 결과에 대한 로그나, 실행 이력들을 저장하기 때문에 개발자가 이 테이블에 직접적으로 Write를 하거나 하는 일들은 없습니다. 주로 조회용 메타 테이블로 이해를 하고 있습니다.

마무리하며

요번 글에서는 배치 작업을 구현해보기 위해서 배치 작업의 정의와 특징들을 알아봤습니다. 어떤 작업들을 배치로 처리해야하는지 배치 작업이 가지는 특징과 장점들을 이해해볼 수 있었습니다. 이번 기회를 통해 Spring-Batch의 전반적인 아키텍처들을 익혀보고 세부 구성요소들을 접해볼 수 있었습니다. 다음글에서는 Spring-Batch를 실제로 응용/적용해보는 글을 다음 글에 이어서 작성해보도록 하겠습니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.