fix(batch): orphan trigger remove
This commit is contained in:
부모
43c28eeccd
커밋
0da81a7471
@ -29,7 +29,9 @@ public class QuartzConfig {
|
|||||||
SchedulerFactoryBean factory = new SchedulerFactoryBean();
|
SchedulerFactoryBean factory = new SchedulerFactoryBean();
|
||||||
factory.setJobFactory(springBeanJobFactory(applicationContext));
|
factory.setJobFactory(springBeanJobFactory(applicationContext));
|
||||||
factory.setOverwriteExistingJobs(true);
|
factory.setOverwriteExistingJobs(true);
|
||||||
factory.setAutoStartup(true);
|
// SchedulerInitializer에서 직접 start() 호출하므로 자동 시작 비활성화
|
||||||
|
// 자동 시작 시 JDBC Store의 기존 trigger가 로드되어 중복 실행 발생 가능
|
||||||
|
factory.setAutoStartup(false);
|
||||||
// DataSource는 Spring Boot가 자동 주입 (application.yml의 spring.datasource 사용)
|
// DataSource는 Spring Boot가 자동 주입 (application.yml의 spring.datasource 사용)
|
||||||
|
|
||||||
return factory;
|
return factory;
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import org.springframework.context.event.EventListener;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 애플리케이션 시작 시 DB에 저장된 스케줄을 Quartz에 자동 로드
|
* 애플리케이션 시작 시 DB에 저장된 스케줄을 Quartz에 자동 로드
|
||||||
@ -34,11 +35,15 @@ public class SchedulerInitializer {
|
|||||||
log.info("========================================");
|
log.info("========================================");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 기존 orphan trigger 전체 정리 (이전 실행에서 남은 잔여 trigger 제거)
|
||||||
|
cleanupOrphanTriggers();
|
||||||
|
|
||||||
// DB에서 활성화된 스케줄 조회
|
// DB에서 활성화된 스케줄 조회
|
||||||
List<JobScheduleEntity> activeSchedules = scheduleRepository.findAllActive();
|
List<JobScheduleEntity> activeSchedules = scheduleRepository.findAllActive();
|
||||||
|
|
||||||
if (activeSchedules.isEmpty()) {
|
if (activeSchedules.isEmpty()) {
|
||||||
log.info("활성화된 스케줄이 없습니다.");
|
log.info("활성화된 스케줄이 없습니다.");
|
||||||
|
startSchedulerIfNeeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,10 +72,7 @@ public class SchedulerInitializer {
|
|||||||
log.info("========================================");
|
log.info("========================================");
|
||||||
|
|
||||||
// Quartz 스케줄러 시작
|
// Quartz 스케줄러 시작
|
||||||
if (!scheduler.isStarted()) {
|
startSchedulerIfNeeded();
|
||||||
scheduler.start();
|
|
||||||
log.info("Quartz 스케줄러 시작됨");
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("스케줄러 초기화 중 에러 발생", e);
|
log.error("스케줄러 초기화 중 에러 발생", e);
|
||||||
@ -79,6 +81,7 @@ public class SchedulerInitializer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 개별 스케줄을 Quartz에 등록
|
* 개별 스케줄을 Quartz에 등록
|
||||||
|
* Trigger를 먼저 명시적으로 제거한 후 Job을 삭제하여 orphan trigger 방지
|
||||||
*
|
*
|
||||||
* @param schedule JobScheduleEntity
|
* @param schedule JobScheduleEntity
|
||||||
* @throws SchedulerException Quartz 스케줄러 예외
|
* @throws SchedulerException Quartz 스케줄러 예외
|
||||||
@ -88,7 +91,13 @@ public class SchedulerInitializer {
|
|||||||
JobKey jobKey = new JobKey(jobName, "batch-jobs");
|
JobKey jobKey = new JobKey(jobName, "batch-jobs");
|
||||||
TriggerKey triggerKey = new TriggerKey(jobName + "-trigger", "batch-triggers");
|
TriggerKey triggerKey = new TriggerKey(jobName + "-trigger", "batch-triggers");
|
||||||
|
|
||||||
// 기존 스케줄 확인 및 삭제
|
// 1. 기존 Trigger 명시적 제거 (orphan trigger 방지)
|
||||||
|
if (scheduler.checkExists(triggerKey)) {
|
||||||
|
scheduler.unscheduleJob(triggerKey);
|
||||||
|
log.debug("기존 Quartz Trigger 제거: {}", triggerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 기존 Job 삭제 (연결된 trigger도 함께 삭제됨)
|
||||||
if (scheduler.checkExists(jobKey)) {
|
if (scheduler.checkExists(jobKey)) {
|
||||||
scheduler.deleteJob(jobKey);
|
scheduler.deleteJob(jobKey);
|
||||||
log.debug("기존 Quartz Job 삭제: {}", jobName);
|
log.debug("기존 Quartz Job 삭제: {}", jobName);
|
||||||
@ -118,4 +127,47 @@ public class SchedulerInitializer {
|
|||||||
log.debug(" → 다음 실행 예정: {}", trigger.getNextFireTime());
|
log.debug(" → 다음 실행 예정: {}", trigger.getNextFireTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quartz 스케줄러가 아직 시작되지 않았으면 시작
|
||||||
|
*/
|
||||||
|
private void startSchedulerIfNeeded() throws SchedulerException {
|
||||||
|
if (!scheduler.isStarted()) {
|
||||||
|
scheduler.start();
|
||||||
|
log.info("Quartz 스케줄러 시작됨");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 앱 시작 시 batch-triggers 그룹의 모든 기존 trigger와 batch-jobs 그룹의 모든 기존 job을 제거
|
||||||
|
* JDBC Store에 잔존하는 orphan trigger로 인한 중복 실행 방지
|
||||||
|
*/
|
||||||
|
private void cleanupOrphanTriggers() throws SchedulerException {
|
||||||
|
// batch-triggers 그룹의 모든 trigger 제거
|
||||||
|
Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(
|
||||||
|
org.quartz.impl.matchers.GroupMatcher.triggerGroupEquals("batch-triggers"));
|
||||||
|
if (!triggerKeys.isEmpty()) {
|
||||||
|
log.info("기존 trigger {} 개 정리 시작 (batch-triggers 그룹)", triggerKeys.size());
|
||||||
|
for (TriggerKey tk : triggerKeys) {
|
||||||
|
scheduler.unscheduleJob(tk);
|
||||||
|
log.debug(" Trigger 제거: {}", tk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// batch-jobs 그룹의 모든 job 제거
|
||||||
|
Set<JobKey> jobKeys = scheduler.getJobKeys(
|
||||||
|
org.quartz.impl.matchers.GroupMatcher.jobGroupEquals("batch-jobs"));
|
||||||
|
if (!jobKeys.isEmpty()) {
|
||||||
|
log.info("기존 job {} 개 정리 시작 (batch-jobs 그룹)", jobKeys.size());
|
||||||
|
for (JobKey jk : jobKeys) {
|
||||||
|
scheduler.deleteJob(jk);
|
||||||
|
log.debug(" Job 제거: {}", jk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!triggerKeys.isEmpty() || !jobKeys.isEmpty()) {
|
||||||
|
log.info("기존 스케줄 정리 완료: trigger {} 개, job {} 개 제거",
|
||||||
|
triggerKeys.size(), jobKeys.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -253,6 +253,7 @@ public class ScheduleService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Quartz에 Job 등록
|
* Quartz에 Job 등록
|
||||||
|
* Trigger → Job 순서로 명시적 제거 후 새로 등록하여 orphan trigger 방지
|
||||||
*
|
*
|
||||||
* @param entity JobScheduleEntity
|
* @param entity JobScheduleEntity
|
||||||
* @throws SchedulerException Quartz 스케줄러 예외
|
* @throws SchedulerException Quartz 스케줄러 예외
|
||||||
@ -262,6 +263,18 @@ public class ScheduleService {
|
|||||||
JobKey jobKey = new JobKey(jobName, "batch-jobs");
|
JobKey jobKey = new JobKey(jobName, "batch-jobs");
|
||||||
TriggerKey triggerKey = new TriggerKey(jobName + "-trigger", "batch-triggers");
|
TriggerKey triggerKey = new TriggerKey(jobName + "-trigger", "batch-triggers");
|
||||||
|
|
||||||
|
// 1. 기존 Trigger 명시적 제거 (orphan trigger 방지)
|
||||||
|
if (scheduler.checkExists(triggerKey)) {
|
||||||
|
scheduler.unscheduleJob(triggerKey);
|
||||||
|
log.debug("기존 Quartz Trigger 제거: {}", triggerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 기존 Job 삭제 (연결된 trigger도 함께 삭제됨)
|
||||||
|
if (scheduler.checkExists(jobKey)) {
|
||||||
|
scheduler.deleteJob(jobKey);
|
||||||
|
log.debug("기존 Quartz Job 삭제: {}", jobName);
|
||||||
|
}
|
||||||
|
|
||||||
// JobDetail 생성
|
// JobDetail 생성
|
||||||
JobDetail jobDetail = JobBuilder.newJob(QuartzBatchJob.class)
|
JobDetail jobDetail = JobBuilder.newJob(QuartzBatchJob.class)
|
||||||
.withIdentity(jobKey)
|
.withIdentity(jobKey)
|
||||||
@ -277,23 +290,9 @@ public class ScheduleService {
|
|||||||
.forJob(jobKey)
|
.forJob(jobKey)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 기존 Job 삭제 후 등록
|
// Quartz에 스케줄 등록
|
||||||
try {
|
scheduler.scheduleJob(jobDetail, trigger);
|
||||||
scheduler.deleteJob(jobKey);
|
log.info("Quartz에 스케줄 등록 완료: {} (Cron: {})", jobName, entity.getCronExpression());
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug("기존 Job 삭제 시도: {}", jobName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Job 등록
|
|
||||||
try {
|
|
||||||
scheduler.scheduleJob(jobDetail, trigger);
|
|
||||||
log.info("Quartz에 스케줄 등록 완료: {} (Cron: {})", jobName, entity.getCronExpression());
|
|
||||||
} catch (ObjectAlreadyExistsException e) {
|
|
||||||
log.warn("Job이 이미 존재함, 재시도: {}", jobName);
|
|
||||||
scheduler.deleteJob(jobKey);
|
|
||||||
scheduler.scheduleJob(jobDetail, trigger);
|
|
||||||
log.info("Quartz에 스케줄 재등록 완료: {} (Cron: {})", jobName, entity.getCronExpression());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user