본문 바로가기

개발/Linux & DevOps

Spring Batch 초기 시스템 구축하며 고민했던 것

 

먼가 둘이 닮지 않았나...?

 

Spring Batch 개발하면서 실제로 고민했고 구현했던 것들

1. 들어가며

Spring Batch는 구조 자체는 잘 만들어진 프레임워크다.
하지만 실제 운영 환경에 들어가면,
공식 문서만으로는 설명되지 않는 지점들을 계속 마주치게 된다.

이 글은 Spring Batch를 사용하면서
“어떻게 만들었는가”보다 “어떻게 운영했는가”에 초점을 맞춘 정리다.


2. Batch 서버와 실행 주체를 분리한 이유

초기에는 Jenkins에서 직접 Batch를 실행하는 구조도 검토했다.
하지만 최종적으로는 다음 구조를 선택했다.

Jenkins (스케줄 / 트리거)
   ↓ SSH
Batch Execution Server
   → java -jar batch.jar

이렇게 분리한 이유

  • Jenkins는 스케줄러
  • Batch 서버는 실행 책임
  • 장애 시 책임 경계가 명확해짐
  • Jenkins 재기동 ≠ Batch 중단

Spring Batch는 장시간 실행되는 경우가 많기 때문에
실행 프로세스의 독립성이 중요했다.

SCDF 도 고민했지만 초기 러닝커브가 Jenkins 대비 컸고,
비개발자 운영자가 Jenkins UI 를 더 선호했다.


3. Jenkins 이중화 환경에서의 Batch Job 운영

Jenkins를 2대로 운영하면서
다음 원칙을 유지했다.

  • 한쪽 서버: 모든 Job Enabled
  • 다른 서버: 모든 Job Disabled
  • 설정은 동기화, 실행은 단일화

이를 통해:

  • 이중 스케줄 실행 방지
  • 장애 시 빠른 전환 가능
  • Spring Batch JobRepository 중복 실행 리스크 제거

Spring Batch는 동시에 실행되면 안 되는 Job이 많기 때문에
스케줄러 이중화는 특히 조심해야 했다.


4. Spring Batch 5 기준 ItemWriter 구성 경험

배치마다 특서이 너무 달라서 Writer 설계에서 가장 많이 고민한 부분은 다음이었다.

  • 하나의 Step에서
  • 서로 다른 테이블 / 목적지로
  • 동시에 Write 해야 하는 요구사항
    • 휴대폰결제 통합결제내역, 상세결제내역은 기본, 예를 들어 추가적으로 삼성페이 휴대폰결제를 통해 들어올 경우 별도 내역에 기재 필요 등등

이를 위해 CompositeItemWriter를 사용했고,
내부에 다수의 JdbcBatchItemWriter를 두는 구조를 선택했다.

여기서 중요했던 포인트

  • Writer 간 트랜잭션 경계
  • Step 종료 시 리소스 정리 시점
  • Connection leak 가능성

Spring Batch 5에서는
Writer lifecycle을 정확히 이해하지 않으면
운영 환경에서 미묘한 문제가 생길 수 있다.


5. Mapper 계층을 분리한 이유

Batch에서는 흔히 Reader → Writer로 바로 넘기지만,
실제 운영에서는 다음 요구가 생겼다.

  • Read 모델 ≠ Write 모델
  • 테이블 변경에 대한 영향 최소화
  • Writer 재사용성 확보

그래서 다음 구조를 유지했다.

Reader → Domain Object
          ↓
        Mapper
          ↓
Writer DTO

이를 통해:

  • Batch 로직과 DB 스키마 결합도 감소
  • Writer 교체 비용 감소
  • 테스트 용이성 증가

6. Job 실패를 “정상 상태”로 취급한 이유

운영하다 보면 Batch 실패는 피할 수 없다.

중요한 건:

실패를 없애는 것이 아니라
실패를 예측 가능하게 만드는 것

그래서 다음을 기본 전제로 두었다.

  • Job 실패 시:
    • 알람 전송
    • 로그 기준 명확화
  • 재실행 가능 구조 유지
  • 데이터 정합성은 JobRepository 기준으로 판단

Spring Batch의 강점은
실패를 전제로 설계되었다는 점이다.

실제 로직을 보면, 매월 1일, 7일, 21일 31일 이렇게 간헐적 날짜만 실행되야하는 배치가 있는데,
이런 배치의 경우 매일 실행으로 설정해놓고 데이터 유입이 있을 시에만 실행 성공, 없을땐 실패가 난다.

그래서 데이터가 없어서 실패가 난 경우 의도적 실패인데,
트리거의 주체인 젠킨스에선 SUCCESS, FAILED, STOPPED 가 일반적인 결과 상태 뿐이다.

그래서 의도적 실패의 경우 STOPPED 로 설정하고, EXIT코드를 보이도록 했다.
그 외에도 너무 다양한 케이스가 많아서 다양한 EXIT 코드를 정의했었다.


7. 설정 관리 전략

Batch 특성상 환경별 설정이 많다.

  • Chunk size
  • Thread count
  • 대상 기간
  • 실행 여부 플래그

이를 코드에 박지 않고:

  • 배치 실행에 파라미터로 주입
  • 운영 중 조정 가능 구조 유지

Batch는 “코드 배포”보다
설정 변경이 더 자주 발생한다.


8. Spring Batch를 쓰며 느낀 점

Spring Batch는:

  • 단순 작업에는 과하고
  • 복잡한 작업에는 강력하다
    • 때에 따라 Reader, Processor, Writer 의 고정적 구조로 인해 한계를 느끼기도 한다. Spring Batch 스럽게 짜야할까? 아니면 내가 생각한 비즈니스 로직에 맞춰야할까? 사이에서 고민도 많이 한다. 이를테면 Tasklet에 무엇이든 구현할 수 있는데, 이렇게 되면 Spring Batch 스럽지 못했다.

마무리

이 글은 특정 기능 설명이 아니라
Spring Batch를 실제 서비스 환경에서 사용하며
의식적으로 선택했던 설계 포인트에 대한 기록이다.

앞으로 Batch를 도입하거나
이미 운영 중인 시스템을 개선하려는 사람에게
작은 참고 자료가 되길 바란다.

반응형