IT/SpringBoot

Spring Batch 기동 시 JOB 상태를 STARTED에서 FAILED 로 변경하기

twofootdog 2022. 3. 17. 17:39

이번 글에서는 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로 변경하는 로직을 수동으로 호출할 수 있게 개발해야 할 것 같다)