서론


오랜만에 글을 작성을 하면서 어떤 글을 작성할까 고민이 되었는데 최근에 Spring Batch 오픈소스 기여를 하면서 해당 내용에 대해서 글을 작성하면 좋을 거 같아서 해당 내용에 대해서 글을 작성합니다.

가장 기본적으로 스프링 배치란 무엇일까? 정의에 대해서 설명을 하겠습니다.

저는 내부 백오피스 개발을 진행하면서 스프링 배치를 학습하고 사내에 도입한 경험이 있으면서 배치란 무엇일까? 고민을 하게 되었습니다.

다른 블로그에서는 스프링 배치에 대해서 설명할 때 다음과 같이 많이 설명을 합니다.

대용량 데이터를 처리하기 위한 프레임워크로, 스프링 프레임워크 기반에서 작동합니다. 일반적으로 배치 작업은 대량의 데이터를 처리하거나, 주기적이고 반복적인 작업을 실행하는 데 사용되며, 스프링 배치는 이러한 작업을 효율적이고 안정적으로 처리할 수 있는 대표적으로 아래와 같은 기능들을 제공합니다.

제 생각도 비슷하지만 조금 생각이 다릅니다. 스프링 배치란 대용량 데이터를 처리하는 프레임워크로, 사람과 상호 작용 없이 이어지는 프로그램의 실행입니다.

API vs Batch: 핵심 차이점

가장 중요한 포인트는 사람과의 상호작용 유무 라고 생각합니다. API로 만든 로직과 배치로 만든 로직을 비교해보겠습니다.

API (웹 애플리케이션)

  • 사용자가 요청하고 응답을 기다립니다.
  • 처리가 느려도 Loading 화면이나 다양한 UX 정책으로 대응할 수 있습니다.
  • 사용자에게 진행 상황을 보여줄 수 있습니다.

Batch (배치 작업)

  • 사람과 상호작용 없이 무인으로 실행됩니다.
  • 일반적으로 특정 시간(스케줄)에 실행됩니다.
  • 정해진 시간 내에 반드시 완료되어야 합니다.
  • UX로 지연을 보완할 수 없고, SLA(Service Level Agreement)를 준수해야 합니다.

실무 예시: 출고 배치

출고 도메인을 예시로 살펴보겠습니다. 1시간마다 배치를 실행하여 상품을 배송한다고 가정해봅시다.

정상 케이스

  • 10건이든 10만건이든 모두 1시간 이내에 출고 작업이 완료되어야 합니다.

문제 케이스

  • 10만건 처리 중 1차 배치에서 5만건만 출고되고 5만건이 pending 상태로 남았다고 가정합니다.
  • 1시간 후 2차 배치가 실행되면 pending 상태인 5만건을 다시 조회하게 됩니다.
  • 이때 중복 처리, 재고 오류, 이중 출고 등의 심각한 문제가 발생할 수 있습니다.

본론


이제부터 우리는 물류 도메인 회사에 근무를 한다고 가정하고 모든 내용을 학습하겠습니다.

처음 배치를 학습할 때 이런 고민을 하게 됩니다. 배치는 대용량 작업을 수행할 때만 사용해야 하는가? 저 역시도 이러한 고민이 있었습니다.

대용량 작업도 아닌데 스프링 배치를 사용하면 오버 엔지니어링이라고 판단하지 않을까? 단순한 배치의 경우 Mybatis나 JdbcTemplate으로도 충분히 처리할 수 있기 때문입니다.

제 생각에는 오버엔지니어링 여부는 업무 환경과 팀의 숙련도에 따라 달라진다고 봅니다. 팀이 배치를 잘 다룬다면 굳이 API 기반으로 억지로 만들 필요가 없습니다.

그리고 대용량의 기준도 사실 모호합니다. 저 같은 경우는 300만건을 기준으로 작업을 수행했고 스프링 배치의 구조화된 방식 덕분에 관리가 쉬워서 적극적으로 활용했습니다.

만약 배치를 처음 도입한다면 대용량에 너무 신경쓰지 말고 스케줄링 관점에서 시작해도 괜찮다고 생각합니다.

사용 사례

배치를 이용해서 많은 작업을 수행하였지만 배치를 잘 사용했다고 느끼고 많은 효율을 가질 수 있었던 2가지 프로젝트를 간단하게 소개하겠습니다.

1. 당일 출고 시스템

네이버 당일출고, 쿠팡 당일배송과 비슷한 맥락을 가지고 있습니다. 많은 B2C 채널에서 수집된 주문을 다양한 요구사항에 맞추어서 풀필먼트에 사용자에게 배달을 하도록 처리를 합니다. 이때 재고의 정합성, 외부 API의 이해 관계, 이중화된 출고 센터의 비즈니스를 포함하여 해결합니다.

2. 데이터 마이그레이션

시스템을 version2로 마이그레이션을 진행하면서 테이블 설계가 변경이 되며 그에 맞추어 데이터 ETL 작업 및 마이그레이션을 수행하였습니다. 해당 방식에서 배치를 진짜 잘 사용했다고 생각을 했던 상황이었습니다. 간단하게 설명하자면 저희는 1개의 DB에서 개발, 운영을 동시에 하면서 SQL Script로 마이그레이션을 하면 데드락, API기반으로 마이그레이션을 10시간이 넘는 시간이 걸렸습니다. 하지만 Batch를 이용해서 30분 이내에 모든 작업을 수행할 수 있었고 데이터 정합성 100%를 맞추었습니다.

Spring Batch 오픈소스 기여

실무에서 배치를 사용하면서 겪었던 문제들을 해결하는 과정에서 Spring Batch 오픈소스에 기여하게 되었습니다. 주로 멀티스레드 환경에서 발생하는 동시성 이슈와 버그 수정에 집중했습니다.

병렬 처리 환경에서의 Race Condition

실무에서 처리 속도를 높이기 위해 멀티스레드로 청크를 병렬 처리하는 경우가 많습니다. 이때 StepContributionfilterCountprocessSkipCount에서 race condition이 발생하는 문제를 발견했습니다.

여러 스레드가 동시에 카운트를 증가시키면서 정확한 집계가 안되는 문제였고 이를 동기화 처리로 해결했습니다. #5188

Graceful Shutdown 개선

배치 운영 중 서버를 재시작하거나 배포할 때 graceful shutdown이 중요합니다. 하지만 종료 과정에서 race condition으로 인해 OptimisticLockingFailureException이 발생하는 문제가 있었습니다.

배치가 정상적으로 종료되지 않고 예외를 던지면서 실패 처리되는 문제였고 종료 시점의 상태 업데이트 로직을 개선했습니다. #5217

Regression Bug 수정

Spring Batch 업데이트 과정에서 발생한 회귀 버그들도 수정했습니다.

MongoDB JobRepository 버그 MongoDB를 JobRepository로 사용할 때 MongoStepExecutionDao.countStepExecutions()에서 stepName 파라미터가 무시되는 버그가 있었습니다. 이로 인해 startLimit 기능이 제대로 동작하지 않았고 같은 Job의 모든 Step 실행 횟수를 카운트하는 문제를 수정했습니다. #5220

파티셔닝 재시작 버그 파티셔닝을 사용할 때 allowStartIfComplete=true 옵션을 설정해도 COMPLETED 상태인 파티션이 재시작되지 않는 버그가 있었습니다. SimpleStepExecutionSplitter의 로직을 수정하여 완료된 파티션도 재시작 가능하도록 개선했습니다. #5238

코드 품질 개선

작은 개선 사항들도 기여했습니다. JobParameterJobOperatorTestUtils 생성자에 null 체크를 추가하고 에러 메시지 오타를 수정했습니다. #5050, #5052

결론


이번 글에서는 Spring Batch의 정의부터 실무 사용 사례, 그리고 오픈소스 기여 경험까지 다뤄봤습니다.

배치는 단순히 대용량 데이터를 처리하는 도구가 아니라 사람과의 상호작용 없이 정해진 시간에 안정적으로 작업을 수행해야 하는 시스템입니다. 실무에서 당일 출고 시스템과 데이터 마이그레이션을 진행하면서 배치의 구조화된 방식이 얼마나 관리하기 쉽고 효율적인지 경험했습니다.

특히 멀티스레드 환경에서 발생하는 동시성 이슈들을 해결하면서 Spring Batch 내부를 깊이 이해하게 되었고 이를 통해 오픈소스에 기여할 수 있었습니다.

다음 글에서는 Spring Batch 6의 새로운 기능들과 실무에서 자주 사용하는 패턴들을 구체적인 코드와 함께 소개하겠습니다.


Back to top

This site uses Just the Docs, a documentation theme for Jekyll.