본문 바로가기
TWIL

[TWIL] Quartz : Multi WAS 환경에서의 배치 스케줄링 방식 이해하기

by amungstudy 2025. 4. 5.

특정한 시각이나 일정한 시간 간격을 두고 주기적으로 실행해야 하는 작업을 스케줄링 작업이라고 하는데요.

주로 백그라운드에서 반복적으로 수행해야 하는 작업입니다.

 

SpringBoot 환경에서 스케줄링 작업을 구현할 때는 2가지 방식을 사용할 수 있습니다. 

 

1. Spring Scheduler

  • 별도의 추가적인 의존성 필요x 스프링 프레임워크에서 제공하는 기능
  • @EnableScheduling 어노테이션 사용

2. Quartz

  • 오픈소스 Java 스케줄링 라이브러리 (SpringQuatz 의존성을 받을수도, 따로 사용할수도 있다)
  • Scheduler간의 Clustering 기능
  • Scheduler 실패에 대한 후처리 가능
  • 이벤트 처리 가능

이렇게 Quartz로 스케줄링을 구현하면 구현이 복잡해지긴 하지만, 세부 설정이 가능해지는 이점이 있습니다. 

보통 대규모 배치 작업을 구현할 때는 Quartz  + Spring Batch 조합을 많이 사용한다고 합니다.

 

그리고 현재 제가 속한 팀에서는 Quartz만 사용하고 있어요.

 

Quartz 라이브러리 관련 개념은 아래 글에 따로 작성 해두었습니다. 

https://amungstudy.tistory.com/432


Multi WAS 환경에서의 스케줄링 작업 시 고려사항

 

현재 저희 팀의 시스템은 단일 또는 이중화로 구성이 가능한데요. 

만약 이중화로 구성이 되면 배치 작업에 어떤 문제가 발생할까요?

시나리오 :

  • 어떤 시스템에서 스케줄러가 외부 시스템에서 사용자 정보를 받아와 DB에 저장해야한다
  • 이 시스템은 WAS 2대로 이중화되어 있고, Quartz로 스케줄링을 한다

→ A와 B가 동시에 사용자 저장 작업을 실행하게됩니다. 즉, 동일한 정보가 두 번씩 저장되는 버그가 발생합니다.

 

이 때 Quartz 클러스터링 기능을 사용하면 한 서버만 Job을 실행할 수 있도록 설정할 수 있습니다. 

 


✅ Quartz 클러스터링이란?

Quartz는 여러 WAS 인스턴스가 동시에 실행되더라도,
Job은 오직 하나의 인스턴스에서만 실행되도록 보장하는 기능입니다. 

 

사용하기 위해서는 아래와 같은 설정이 필요합니다. 

1. isClustered: true 설정

  • Quartz가 DB의 QRTZ_LOCKS 테이블을 이용해서 분산 Lock 처리 시작
  • 단 하나의 인스턴스에서만 Job이 실행되도록 자동 조율됨

2. DB에 Quartz 스키마 생성

  • 메타 정보를 관리할 테이블이 필요하며, Quartz에서 요구하는 스키마를 따라 DB에 미리 생성되어 있어야 합니다
    • Quartz library 내부에서 sql 파일 확인 가능 (org.quartz.impl.jdbcjobstore.table_[DB종류].sql)
  • QRTZ_LOCKS, QRTZ_SCHEDULER_STATE 등의 테이블 필요 
  • quartz.properties나 Spring 설정으로 JDBCJobStore 사용 명시

 

✅ Job 등록을 오직 한 인스턴스에서만 하도록 제어해보자

그냥 클러스터링 설정만 하고 같은 코드를 두개의 was에서 실행하면 다음과 같은 에러가 발생합니다.

 

Unable to store Job : '<job그룹, job이름>', because one already exists with this identification.

이는 두 인스턴스가 동시에 동일한 Job을 등록하면서 충돌이 발생하는 전형적인 클러스터링 문제입니다.

 

따라서 오직 한 인스턴스만 Job을 등록하도록 처리 해야 합니다.

 

if (!scheduler.checkExists(jobKey)) { // job이 이미 존재하는지 확인
    scheduler.scheduleJob(jobDetail, trigger); // job 등록
    LOGGER.info("✅ Job [{}] successfully registered.", jobKey);
} else {
    LOGGER.info("⚠️ Job [{}] already exists. Skipping registration.", jobKey);
}

 

이런식으로 scheduler.checkExists API를 사용하면 scheduler에 해당 job이 존재하는지 확인할 수 있습니다.

 

Quartz의 jobStore 설정이 JobStoreTX 경우 scheduler.checkExists(jobKey) 를 호출하면

내부적으로 QRTZ_JOB_DETAILS 테이블을 조회해서 해당 JobKey가 존재하는지를 확인합니다.

  • QRTZ_JOB_DETAILS 테이블 : 등록된 Job의 정의, JobKey 정보가 존재
  • JobKey는 name, group 생성자를 가지는데, DB에 JOB_NAME, JOB_GROUP 으로 매핑되어 저장됨
  • 이때 분산락(FOR UPDATE)을 사용하면 더 안정적으로 동작한다고 함

 

이외에도 스케줄된 트리거가 제시간에 실행되지 못한 상황을 어떻게 처리할지를 설정하는 misfire 설정도 존재합니다.

이 부분은 나중에 정리해보겠습니다.

+ QUARTZ_LOCK 테이블 무슨 테이블인지?  알아보기

 

 

참고 : https://www.quartz-scheduler.org/documentation/quartz-2.3.0/