AI Briefing

배치 스케줄러 내재화: OnQuartz 개발 여정

·2025.11.26 10:05

Quartz와 공통 라이브러리로 외부 Batch Scheduler를 내재화했다.

외부 Batch Scheduler의 운영 비용커스터마이징 한계를 줄이기 위해 사내 Batch 스케줄러 OnQuartz를 구축했다. 기존 서비스 흐름과 Batch 프로젝트 코드는 최대한 유지하고, 기존 서버에 Scheduler를 탑재하되 안정성을 확보하는 방향으로 설계했다.

Scheduler 후보로는 Spring Scheduler와 Quartz Scheduler를 검토했지만, 클러스터링HA를 공식 지원하는 Quartz를 선택했다. MISFIRE_INSTRUCTION_DO_NOTHING을 채택해 중복 실행을 막고, 담당자가 재실행 여부를 판단할 수 있도록 Slack 알림을 붙였다. 또한 clusterCheckinInterval 5초, misfireThreshold 60초로 조정해 리더 교체 시 60초 이내 지연은 신규 Pod에서 정상 처리되도록 했다.

최종 구조는 세 단계로 정리된다.

  • Scheduler → Batch 실행 요청: Quartz가 Batch 수행 API를 호출하고, 상태가 RUNNING인 작업은 SKIP해 중복 실행을 방지
  • 공통 라이브러리 → Batch 실행: Scheduler에는 즉시 응답하고, 비동기로 Batch API를 호출하며 Kafka로 alive 메시지와 결과를 주기 전송
  • Scheduler → 결과 처리: Topic을 consume하고, 일정 시간 이상 메시지가 없으면 FAIL로 처리해 무한 RUNNING을 방지

배포는 BLUE-GREEN 환경을 전제로 리더 선출 문제를 풀었다. Quartz 테이블의 현재 인스턴스 정보를 참고하고 DB Lock으로 리더를 조회해, 최신 Tag의 서버가 리더가 되도록 했으며, 리더 체크는 N초마다 수행해 탈락 시 Standby, 복귀 시 재개되도록 만들었다.

운영 과정에서는 예외도 많았다. @Async를 별도 Executor 없이 쓰면 공통 라이브러리에서 추적이 어려워 명시적 Executor와 TaskDecorator로 호출 스레드의 MDC를 복제해야 했다. 또 서버 장애로 작업이 Queue에 쌓인 뒤 복구되며 중복 실행되는 문제를 막기 위해 TTL 개념을 도입했고, Queue 크기를 제한한 뒤 요청 시각과 현재 시각을 비교해 TTL을 넘은 작업은 즉시 skip하도록 바꿨다.

마지막으로, Batch는 실패했는데 상태가 계속 RUNNING으로 남는 문제도 있었다. 일부 MSA에서 AOP 사전 로직이 실패하면 라이브러리의 afterJob이 실행되지 않아 alive 메시지만 계속 전송되는 구조였기 때문이다. 이 케이스는 예외 처리와 구조 변경으로 afterJob 보장을 강화해야 하는 과제로 남았다.

3월부터 내재화를 시작해 약 2개월의 테스트를 거쳤고, 9월에는 전사 100% 전환을 완료했다. 외부 솔루션 비용을 줄이는 동시에, 배치 관리와 모니터링을 위한 대시보드까지 갖추며 더 유연한 Batch 환경을 만들었다.

이 요약은 원문 이해를 돕기 위한 큐레이션입니다. 저작권은 원저작자에게 있으며, 정확한 내용과 맥락은 원문을 확인하세요.

요약 오류, 출처 표기 문제, 삭제 요청은 문의 · 건의로 알려주세요.