Part 3. Quartz 클러스터 아키텍처: JobStore, Misfire, 대규모 스케줄 관리
Quartz를 단순 스케줄러가 아니라 운영 제어 시스템으로 활용하기 위한 JobStore, Misfire, 클러스터 설계 기준을 다룬다.
Series: Spring Boot 배치 전략 완전 정복
총 12편 구성. 현재 3편을 보고 있습니다.
- 01Part 1. 배치의 본질과 분류: 스케줄, 이벤트, 수동, 대량, Near-real-time
- 02Part 2. @Scheduled 실전 운영: 단순함의 대가와 멀티 인스턴스 함정
- 03Part 3. Quartz 클러스터 아키텍처: JobStore, Misfire, 대규모 스케줄 관리CURRENT
- 04Part 4. Spring Batch 핵심: Chunk, 트랜잭션 경계, Restart 가능한 Job 설계
- 05Part 5. Spring Batch 확장: Partition과 Multi-threaded Step의 트레이드오프
- 06Part 6. 수동 배치 전략: REST 트리거, Admin UI, 파라미터 재처리, 롤백
- 07Part 7. DB 대량 조회 전략: OFFSET/LIMIT 한계와 Keyset, ID Range, Covering Index
- 08Part 8. OpenSearch/Elasticsearch 배치 전략: Scroll, Search After, PIT, Bulk, Rollover
- 09Part 9. 분산 환경 배치: Leader Election, Kubernetes CronJob, 락 전략 비교
- 10Part 10. 성능 최적화: 배치 사이즈, 커밋 간격, JVM 메모리, Backpressure
- 11Part 11. 장애 대응 아키텍처: Partial Failure, Poison Data, DLQ, 재시도, 멱등성
- 12Part 12. 통합 레퍼런스 아키텍처와 최종 선택 가이드

출처: Pexels - Black Alarm Clock on Desk
버전 기준
- Java 21
- Spring Boot 3.3.x
- Spring Batch 5.2.x
- Quartz 2.3.x
- PostgreSQL 15
- OpenSearch 2.x
1) 문제 제기
@Scheduled로는 감당하기 어려운 시점이 있다. 대표적으로 다음 조건이다.
- 작업 수가 수백 개 이상으로 증가한다.
- 작업별 우선순위, 캘린더 제외일, 재시도 정책이 다르다.
- 장애 후 "놓친 실행"을 어떤 규칙으로 보정할지 중요하다.
이 지점에서 Quartz는 단순 cron 도구가 아니라 "스케줄 상태 저장소 + 실행 제어기"가 된다. 그러나 Quartz를 도입했다고 자동으로 안정성이 생기지는 않는다. JobStore와 Misfire 정책을 잘못 잡으면 오히려 장애가 확대된다.
2) 핵심 개념 정리
JobStore 선택
| JobStore | 장점 | 단점 | 권장 상황 |
|---|---|---|---|
| RAMJobStore | 빠르고 단순 | 재기동 시 스케줄 유실, 클러스터 불가 | 로컬 개발/테스트 |
| JDBCJobStore | 영속성, 클러스터 지원, 감사 추적 | DB 부하/스키마 관리 필요 | 운영 환경 기본 선택 |
운영 환경에서는 사실상 JDBCJobStore가 표준이다. 클러스터 모드에서 Quartz 인스턴스들은 같은 QRTZ_* 테이블을 공유하고, 락(row lock)으로 실행 권한을 조정한다.
Misfire 정책
Misfire는 "정해진 시점에 실행하지 못한 트리거"를 의미한다. 정책을 잘못 선택하면 장애 복구 시 트래픽 폭탄이 발생한다.
MISFIRE_INSTRUCTION_FIRE_NOW: 즉시 실행. 누락 건수를 빠르게 보정하나 급격한 부하 가능.MISFIRE_INSTRUCTION_DO_NOTHING: 다음 스케줄까지 건너뜀. 정합성보다 안정성 우선.- 커스텀 재스케줄: 도메인 중요도별로 보정량 제한.
클러스터 다이어그램

출처: Pexels - Engineer beside server racks
3) 코드 예시
예시 A: Quartz 클러스터 설정 (Spring Boot)
spring:
quartz:
job-store-type: jdbc
properties:
org.quartz.scheduler.instanceName: batchScheduler
org.quartz.scheduler.instanceId: AUTO
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
org.quartz.jobStore.isClustered: true
org.quartz.jobStore.clusterCheckinInterval: 15000
org.quartz.threadPool.threadCount: 20
예시 B: 동시 실행 방지 Job
@DisallowConcurrentExecution
public class ReindexJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
String tenantId = context.getMergedJobDataMap().getString("tenantId");
try {
// 실제 재색인 로직
reindexTenant(tenantId);
} catch (Exception ex) {
throw new JobExecutionException(ex, true);
}
}
private void reindexTenant(String tenantId) {
// id-range 또는 search-after 기반으로 분할 실행
}
}
예시 C: Misfire 모니터링 SQL
SELECT trigger_name,
trigger_group,
next_fire_time,
prev_fire_time,
misfire_instr
FROM qrtz_triggers
WHERE next_fire_time < EXTRACT(EPOCH FROM NOW()) * 1000
ORDER BY next_fire_time ASC
LIMIT 200;
예시 D: Quartz 실행 이력 키셋 조회
SELECT id, scheduler_name, job_name, status, started_at
FROM batch_job_execution
WHERE scheduler_name = 'quartz'
AND id > :last_id
ORDER BY id ASC
LIMIT 500;
4) 실제 장애/운영 시나리오
상황: DB 점검으로 25분 다운 이후 Quartz가 복구되자 Misfire 트리거가 1,200건 누적되었다. 정책이 모두 FIRE_NOW라서 복구 직후 동시 실행이 폭증했고, OLTP DB 커넥션 풀이 고갈되며 API 지연까지 발생했다.
분석:
- 중요한 작업(정산)과 덜 중요한 작업(리포트 생성)에 동일 Misfire 정책을 적용했다.
threadCount=50로 높였지만 downstream 제한(DB pool 30)을 고려하지 않았다.- 트랜잭션 격리 수준을
REPEATABLE READ로 통일해 락 유지시간이 증가했다.
개선:
- 도메인별 Misfire 프로필 분리: 정산은 제한적
FIRE_NOW, 리포트는DO_NOTHING. - Quartz 실행 스레드와 DB pool, 외부 API QPS를 동일한 한도 모델로 조정.
- 실패 재시도는 즉시가 아니라 지수 백오프로 분산.
5) 설계 체크리스트
- 운영 환경에서 RAMJobStore를 사용하지 않는가?
- Misfire 정책을 업무 중요도별로 분리했는가?
- Quartz threadCount와 DB/외부 시스템 용량을 함께 튜닝했는가?
-
@DisallowConcurrentExecution적용 범위를 검토했는가? - 클러스터 체크인 간격과 장애 감지 시간을 수치로 정의했는가?
- 실행 이력과 누락 건수(Misfire backlog)를 대시보드로 관측하는가?
6) 요약
Quartz는 "정교한 스케줄 제어"가 필요한 순간 강력한 선택지다. 하지만 JobStore, Misfire, 스레드 설정을 비즈니스 정합성과 인프라 한도에 맞추지 않으면 장애를 증폭할 수 있다.
7) 다음 편 예고
다음 편에서는 Spring Batch의 Chunk 처리 모델을 깊게 다룬다. Reader/Processor/Writer의 트랜잭션 경계, 재시작 전략, 대량 데이터 처리에서 왜 Spring Batch가 표준이 되는지 설명한다.
참고 링크
- Spring Batch Reference
- Quartz Scheduler Documentation
- PostgreSQL Transaction Isolation
- 블로그: Idempotency Key API 설계