Spring Batch 기동 시 JOB 상태를 STARTED에서 FAILED 로 변경하기
이번 글에서는 Spring Batch 기동 시 배치 상태를 STARTED에서 FAILED로 변경하는 방법에 대해 알아볼 것이다.
Spring Batch 를 개발하다 보면, 코드 검증 등을 위해서 WAS 재기동하는 경우가 많다.
그런데 Spring Batch의 특정 Job이 실행 중일 때 재기동을 하게 되면 해당 Job은 STARTED 상태로 남아있게 되며, 만약 Job의 중복 실행을 방지하는 코드가 들어가 있게 되면, STARTED 상태인 Job은 실행이 되지 않게 된다.
따라서 매 재기동 시마다 STARTED인 상태의 Job을 FAILED나 COMPLETED 상태로 변경한 후 Job을 실행시켜야 하는 번거로움이 발생한다.
이런 번거로움을 해결하기 위해서는 기동 시 배치 상태를 STARTED에서 FAILED로 변경하는 것이 필요하다(WAS 기동 시 배치 상태가 STARTED인 것은, WAS 내릴 때 해당 배치의 상태를 고려하지 않고 내린 것이 되므로, 상태를 FAILED처리 할 필요가 있다)
해당 작업을 수행하기 위해서는 아래 코드를 작성하면 된다.
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@Component
public class ContextRefreshEventListener implements ApplicationListener<ContextRefreshedEvent> {
private static final Logger log = LogManager.getLogger();
private final JobExplorer jobExplorer;
private final JobRepository jobRepository;
public ContextRefreshEventListener(JobExplorer jobExplorer, JobRepository jobRepository) {
this.jobExplorer = jobExplorer;
this.jobRepository = jobRepository;
}
// Spring 이 기동될 때마다 수행됨. 상태가 STARTED인 Job 전체 조회 후 상태를 FAILD 처리
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
log.info("Container Start. find STARTED STATUS Job and Change to FAILED");
List<String> jobs = jobExplorer.getJobNames();
for (String job : jobs) {
Set<JobExecution> runningJobs = jobExplorer.findRunningJobExecutions(job);
for (JobExecution runningJob : runningJobs) {
try {
if (!runningJob.getStepExecutions().isEmpty()) {
Iterator<StepExecution> iter = runningJob.getStepExecutions().iterator();
while (iter.hasNext()) {
StepExecution runningStep = (StepExecution)iter.next();
if (runningStep.getStatus().isRunning()) {
runningStep.setEndTime(new Date());
runningStep.setStatus(BatchStatus.FAILED);
runningStep.setExitStatus(new ExitStatus("FAILED", "BATCH FAILED"));
jobRepository.update(runningStep);
}
}
}
runningJob.setEndTime(new Date());
runningJob.setStatus(BatchStatus.FAILED);
runningJob.setExitStatus(new ExitStatus("FAILED", "BATCH FAILED"));
jobRepository.update(runningJob);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
}
}
위와 같이 코드를 작성하게 되면, 스프링부트 WAS 기동 시, 상태가 STARTED인 JOB과 해당 JOB의 STEP 중 상태가 STARTED인 STEP을 찾아서 모두 FAILED 처리한다.
한계점 : Spring Batch 서버가 한 개인 경우는 유용하지만, 2개 이상의 서버로 관리하는 경우는 유용하지 않을 수 있다(예를 들면 배치 1번 서버를 재기동한 후 배치 2번 서버를 재기동 하기 전, 그 찰나의 순간에 스케쥴링 되어 있는 배치가 배치 1번 서버에서 돌게 되면, 배치 2번 서버 재기동 시 배치 1번 서버에서 실행된 배치 Job의 상태를 FAILED로 바꿀 수 있기 때문..)
그렇기 때문에 위와 같은 문제를 해결하기 위해서는 재기동 시에는 스케쥴을 중지해 놓고, 모든 서버 재기동 후 스케쥴을 원상복구 해 놓거나, 스케쥴을 동적으로 변경할 수 없는 경우라면 STARTED->FAILED로 변경하는 로직을 수동으로 호출할 수 있게 개발해야 할 것 같다)