Spring Batch 도입부터 운영까지 - #2
(살아남기 위한 구조 개편)

Spring Batch
by hwaneehwanee·Oct 27, 2025

[Spring Batch 도입부터 운영까지 - #1]에서 소개했던 구조는 실제 운영 환경에서도 큰 문제 없이 동작했다.

하지만 시간이 지나면서 배치 모듈 수가 증가했고, 그에 따라 항상 실행 중인 Spring Application 역시 함께 늘어났다. 배치가 실행되지 않는 시간에도 프로세스는 계속 살아있었고, 결국 서버 리소스를 불필요하게 점유하는 구조가 되어버렸다.

목표

구조 개편을 진행하면서 두 가지 원칙을 세웠다.

  • 기존 개발된 코드를 최대한 수정하지 않는다.
  • 운영 방식만 변경하여 리소스 사용량을 줄인다.

특히 이미 운영 중인 배치가 많았기 때문에 많은 양의 리팩토링은 피하고 싶었다.

기존 구조 다시 보기

기존 구조는 Quartz를 이용해 애플리케이션 내부에서 직접 스케줄링을 수행했다.

Spring Application
 ├─ Quartz Scheduler
 ├─ Batch Job
 └─ Running...

애플리케이션이 실행되면 Quartz가 스케줄을 등록하고, 지정된 시간마다 Batch Job을 실행하는 방식이다.

장점도 있었다.

  • Cron을 DB에서 관리 가능
  • 실시간 스케줄 변경 가능
  • Spring Batch와 연동이 편리

하지만 단점도 명확했다.

  • 스케줄러를 위해 프로세스가 항상 실행되어야 함
  • 배치 모듈 수만큼 JVM이 상주
  • 서버 메모리 지속 사용

결국 배치 특성에 맞게 구조를 단순화하기로 했다.

새로운 구조

Quartz를 제거하고 Windows 작업 스케줄러를 사용하기로 했다.

AS-IS

Spring Application
   ↓
Quartz Scheduler
   ↓
Batch Job
   ↓
Keep Running

TO-BE

Windows Scheduler
   ↓
Spring Batch 실행
   ↓
Job 수행
   ↓
프로세스 종료

기존처럼 애플리케이션이 계속 실행되는 것이 아니라,

스케줄 관리를 OS에서 처리하고 스케줄 시간이 되면 OS가 애플리케이션을 실행하고 Job 완료 후 즉시 종료하는 방식이다.

이렇게 되면 배치가 실행되지 않는 시간에는 JVM도 존재하지 않는다.

변경사항 1. Quartz 제거

먼저 Quartz 자동 설정을 비활성화했다.

spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration

기존에는 Quartz가 애플리케이션 시작 시 자동으로 스케줄을 등록했다.

이제는 OS 스케줄러가 실행을 담당하므로 더 이상 Quartz가 필요하지 않았다.


그냥 Quartz를 제거하면되지 왜 비활성화해?

일부 Batch Job은 1분 주기로 실행되고 있었기 때문이다.
해당 Job을 OS 스케줄러로 실행하면 매번 애플리케이션을 기동해야 하는데, 오히려 Job 실행보다 애플리케이션 구동에 더 많은 시간이 소요될 것으로 판단했다.
결국 모든 모듈을 동일한 방식으로 변경하기보다는, 실행 주기가 짧은 Batch Job은 Quartz를 유지하고 그 외 모듈만 OS 스케줄러 기반으로 전환하는 방향을 선택했다.

변경사항 2. Batch Job 자동 실행

기존에는 Quartz가 JobLauncher를 호출하는 구조였다.

Quartz
   ↓
CustomJobLauncher
   ↓
Spring Batch Job

Quartz를 제거하면서 구조도 단순해졌다.

Application Start
   ↓
Spring Batch Job 실행
   ↓
Application Exit

애플리케이션이 시작되면 즉시 Job을 실행하고 종료하도록 변경했다.

변경사항 3. Scheduler 패키지 제거

1편에서 소개했던 구조에는 아래와 같은 공통 패키지가 존재했다.

module-core
 ├─ scheduler
 ├─ service
 ├─ config
 └─ dto

Quartz 제거 후에는 scheduler 패키지가 더 이상 필요하지 않게 되었다.

기존 구조에서 가장 복잡했던 부분이 사라지면서 전체 프로젝트 구조도 훨씬 단순해졌다.

제거된 요소

  • CustomJobRunner
  • CustomJobLauncher
  • Quartz Trigger
  • Cron 동적 관리 기능

결과적으로 팀원이 프로젝트를 이해하기도 쉬워졌다.

결과

구조를 변경한 뒤 가장 체감된 부분은 리소스 사용량이었다.

기존 구조

배치 모듈 증가
   ↓
실행 프로세스 증가
   ↓
메모리 사용량 증가

개선 후

배치 실행 시간
   ↓
프로세스 실행
   ↓
작업 완료
   ↓
프로세스 종료

배치가 실행되지 않는 시간에는 JVM 자체가 존재하지 않기 때문에 서버 자원 사용량이 크게 줄어들었다.

또한 Quartz 관련 코드가 사라지면서 프로젝트 복잡도도 함께 감소했다.

마무리

당시에는 Spring Batch와 Quartz를 조합한 구조가 꽤 괜찮은 설계라고 생각했다.

실제로도 문제 없이 운영되었고, 배포 시 다른 Job에 영향을 주지 않는다는 요구사항도 충족할 수 있었다. 하지만 시간이 지나고 배치 모듈이 늘어나면서, 점점 배치를 실행하기 위한 애플리케이션이 아니라 스케줄러를 유지하기 위한 애플리케이션이 되어가고 있었다.

구조를 단순하게 변경한 뒤에는 서버 리소스 사용량도 줄어들었고, 팀원이 프로젝트를 이해하는 데 필요한 시간도 크게 감소했다.

돌아보면 이번에 개선한 구조는 어쩌면 Spring Batch가 의도한 가장 기본적인 운영 방식에 가까웠을지도 모른다. 그럼에도 당시에는 여러 요구사항과 걱정들이 겹치면서 필요 이상으로 복잡한 설계를 선택했던 것 같다.

물론 그 과정이 완전히 의미 없었던 것은 아니다. 실제로 운영해보면서 어떤 부분이 불필요한 복잡성이었는지 직접 경험할 수 있었고, 덕분에 지금은 요구사항에 맞는 적절한 수준의 설계가 얼마나 중요한지 알게 되었다.

결국 좋은 설계란 복잡한 설계가 아니라, 현재 문제를 가장 단순하게 해결할 수 있는 설계가 아닐까 생각한다.

연관 포스트