특정한 시각이나 일정한 시간 간격을 두고 주기적으로 실행해야 하는 작업을 스케줄링 작업이라고 하는데요.
주로 백그라운드에서 반복적으로 수행해야 하는 작업입니다.
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/
'TWIL' 카테고리의 다른 글
SpringBoot 3.3.x → 3.4.x Migration 트러블슈팅 (0) | 2025.04.20 |
---|---|
[TWIL] DNS와 /etc/hosts (0) | 2025.04.13 |
[TWIL]CPU 과점유 시 확인해야 할 사항들 (0) | 2025.03.21 |
[TWIL]Git upstream과 친해지기 (0) | 2025.03.15 |
[TWIL] Ehcache Replication으로 세션공유하기 (2) | 2025.02.28 |