Spring/Spring Batch

[Spring Batch] 4장 : 잡 설정 및 실행(Configuring and Running a Job) 3

공대키메라 2023. 11. 28. 18:23

이번 시간에는 지난에 보던 글에 이어서 JobRegistry부터 다시 읽어볼 것이다.

(지난 내용이 궁금하면 여기 클릭!)

 

 


 

1. JobRegistry

JobRegistry(그리고 그 부모 인터페이스인 JobLocator) 는 강제적이지는 않지만 context에서 어떤 잡이 사용할 수 있는지 추적하길 원하면 유용할 수 있다. 또한 context가 어디서든지 생성되어왔을 때 application context안에서 job을 중앙에서 모으기에 유용하다. 또 등록된 잡의 다른 속성이나 이름을 수정하기 위해 커스텀 JobRegistry 구현체를 사용할 수 있다.

 

@EnableBatchProcessing을 사용할 때 JobRegistry가 제공된다. 다음 예시는 자신의 JobRegistry를 설정할 수 있는지 보인다.

 

...
// This is already provided via the @EnableBatchProcessing but can be customized via
// overriding the bean in the DefaultBatchConfiguration
@Override
@Bean
public JobRegistry jobRegistry() throws Exception {
	return new MapJobRegistry();
}
...

 

 두 방식으로 JobRegistry를 사용할 수 있다 : bean post processor를 사용해서 혹은 registrar lifecycle component를 사용해서.

 

다음 섹션은 이 두 메커니즘에 대해 설명한다.

 

bean post processor 는 한국말로 하면 빈 후처리기인데 이름에서 예상하듯이 정말 JobRegistry를 빈을 후처리 하는 과정에서 등록해서 이렇게 이름을 붙인건지 궁금하다. 여기서는 아무래도 Job 관련 이야기를 하니 Job관련 빈일 것이다. 과연 그럴까?

registrar lifecycle component도 등록 라이프사리클 컴포넌트? 하여간 읽어봐야 할 것 같다.

 

 

2. JobRegistryBeanPostProcessor

JobRegistrfyBeanPostProcessor는 생성될 때 모든 잡을 등록하는 bean post-processor다.

다음 예시는 자바에서 정의된 job에서 어떻게 JobRegistryBeanPostProcessor를 포함하는지 보인다.

 

@Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
    JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor();
    postProcessor.setJobRegistry(jobRegistry);
    return postProcessor;
}

 

비록 post-processor가 꼭 필요하지는 않아도 예제에서는 id를 받기 위해 자식 context에서 포함될 수 있고 모든 잡이 자동적으로 등록되서 생성되도록 유발할 수 있다. 

 

즉, 다시 말을 정리하면 Bean post-processor으로 Application Context가 올라가면서 bean 등록 시, 자동으로 JobRegistry에 Job을 등록 시켜준다. Job마다 등록할 필요 없이 Job이 빈으로 등록될 때 후처리를 통해
JobRegistry에 등록해준다는 말이다.

그래서 이름도 그 행위 그대~로 "잡 등록 빈후처리기" 인것이다!

아니 왜 이렇게 말을 이렵게 하는거야? (어쩌면 내가 어렵게 해석하는 걸지도...)

 

3. AutomaticJobRegistrar

AutomaticJobRegister는 자식 context를 생성하고 생성된 그러한 context로 부터 job을 등록하는 lifecycle 컴포넌트다.

이것의 한 장점은 자식 context에서 job 이름들이 registry에서 글로벌하게 유일해야만 한다는 점 때문에 의존성이 "natural" 이름을 가질 수 있다. 예를 들어 각각 하나의 Job만 가지고 있지만 모두 reader와 같은 빈 이름을 가진 ItemReader의 다른 정의를 갖는 XML 구성 파일 세트를 생성할 수 있다. 모든 그 파일들이 같은 context에서 import된다면 reader 정의들은 충돌하고 서로 다른것을 오버라이드(override)하지만 automatic registrar는 이러한 것을 피한다. 이를 통해 어플리케이션의 별도 모듈에서 제공된 job들을 더 쉽게 통합할 수 있다.

 

AutomaticJobRegistrar의 역할은 부모 context에서 자식 contexts에 생성된 Job들을 사용하기 위해 자동으로 부모 context의 JobRegistry에 Job들을 등록시킨다.

 

 

다음 예시는 java에서 정의된 job에서 AutomaticJobRegistrar를 어떻게 포함하는지 보인다.

 

@Bean
public AutomaticJobRegistrar registrar() {

    AutomaticJobRegistrar registrar = new AutomaticJobRegistrar();
    registrar.setJobLoader(jobLoader());
    registrar.setApplicationContextFactories(applicationContextFactories());
    registrar.afterPropertiesSet();
    return registrar;

}

 

registrar는 두 개의 필수적인 속성을 가진다 : ApplicationContextFactory의 배열과 JobLoader다. JobLoader는 자식 context의 lifecycle을 관리하고 JobRegistry에 잡을 등록하는데 책임이 있다.

 

ApplicationContextFactory는 자식 context를 생성하는데 책임이 있다. 가장 흔한 사용법은 ClassPathXmlApplicationContextFactory를 사용하는것이다. 이 팩토리의 특징 중에 하나는 기본적으로 부모 context에서 자식까지 설정들을 복사한다는 점이다. 그래서, 예를 들어 PropertyPlaceholderConfigurer 혹은 자식에서 AOP 설정을 재정의할 필요가 없다면 제공받은 것은 부모처럼 똑같다.

 

JobRegistryBeanPostProcessor와의 결합으로 AutomaticJobRegister를 사용할 수 있다. (DefaultJobLoader를 사용하는 한). 예를 들어, 이것은 자식 위치 안에서 뿐만 아니라 주요 부모 context에 정의된 잡이 있다면 바람직할 수 있다.

 

4. JobOperator

전에 논의된 JobRepository는 meta-data에 CRUD 작업을 제공하고 JobExplorer는 metadata에 read-only 작업을 제공한다. 하지만 그 작업들은 job을 멈추고(stopping), 재시작하고(restarting) 혹은 요약하는거 같은 흔한 모니터링 작업을 함께 처리하기 위해 사용될 때 가장 유용하다. Spring batch는 JobOperator 인터페이스로 이러한 형태의 작업을 제공한다.

 

public interface JobOperator {

    List<Long> getExecutions(long instanceId) throws NoSuchJobInstanceException;

    List<Long> getJobInstances(String jobName, int start, int count)
          throws NoSuchJobException;

    Set<Long> getRunningExecutions(String jobName) throws NoSuchJobException;

    String getParameters(long executionId) throws NoSuchJobExecutionException;

    Long start(String jobName, String parameters)
          throws NoSuchJobException, JobInstanceAlreadyExistsException;

    Long restart(long executionId)
          throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException,
                  NoSuchJobException, JobRestartException;

    Long startNextInstance(String jobName)
          throws NoSuchJobException, JobParametersNotFoundException, JobRestartException,
                 JobExecutionAlreadyRunningException, JobInstanceAlreadyCompleteException;

    boolean stop(long executionId)
          throws NoSuchJobExecutionException, JobExecutionNotRunningException;

    String getSummary(long executionId) throws NoSuchJobExecutionException;

    Map<Long, String> getStepExecutionSummaries(long executionId)
          throws NoSuchJobExecutionException;

    Set<String> getJobNames();

}

 

앞의 작업들은 JobLauncher, JobRepository, JobExplorer 그리고 JobRegistry같은 많은 다른 인터페이스들로부터 메소드를 나타낸다. 이러한 이유로 JobOperator를 제공받는 구현체는 많은 의존성을 가진다.

 

다음 예시는 Java에서 SimpleJobOperator에대한 전형적인 빈 정의를 보인다. 

 

 /**
  * All injected dependencies for this bean are provided by the @EnableBatchProcessing
  * infrastructure out of the box.
  */
 @Bean
 public SimpleJobOperator jobOperator(JobExplorer jobExplorer,
                                JobRepository jobRepository,
                                JobRegistry jobRegistry,
                                JobLauncher jobLauncher) {

	SimpleJobOperator jobOperator = new SimpleJobOperator();
	jobOperator.setJobExplorer(jobExplorer);
	jobOperator.setJobRepository(jobRepository);
	jobOperator.setJobRegistry(jobRegistry);
	jobOperator.setJobLauncher(jobLauncher);

	return jobOperator;
 }

 

5.0버전에서 @EnableBatchProcessing 어노테이션은 자동적으로 application context에 job operator bean을 등록한다.

 

관련 method가 무슨 기능을 하는지 정리가 되어 있어서 찾아봤다.

 

출처 :&nbsp;https://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte2:brte:batch_core:job_operator

 

job operator는 JobRepository, JobExplorer의 짬뽕 같은데 모니터링하기 더 좋은 기능들을 제공한다는 것이다.

그래, 이것을 통해 모니터링을 하는 것은 알겠는데 이것도 굳이 왜 필요한지 궁금해서 이에 대해 검색을 했다.

궁금하면 다음 글을 펴서 읽어보길 추천한다.

 

더보기

Me

JobOperator도 또 JobRepository랑 JobExplorer 기능대신에 사용할 수 있는 거잖아? 그런데 이게 또 왜 필요한지 모르겠어.

 

GPT

JobOperator는 Spring Batch에서 제공하는 인터페이스로, 배치 작업(Job)을 실행, 중지, 재시작 및 조회하는 등의 기능을 제공합니다. JobRepository와 JobExplorer는 이와 관련된 기능을 제공하지만, JobOperator는 이들을 더 높은 수준에서 추상화하여 사용자에게 제공합니다.

JobRepository 및 JobExplorer의 역할:

  1. JobRepository: 배치 작업의 실행 상태를 관리합니다. 이는 작업 실행, 상태 업데이트, 중지 신호 처리 등을 포함합니다. 주로 배치 작업의 실행 중인 상태를 관리하는 데 사용됩니다.
  2. JobExplorer: 저장된 배치 작업 및 관련 실행 정보를 조회하는 데 사용됩니다. 이는 주로 작업 실행 이력 조회에 사용됩니다.

JobOperator의 역할과 필요성:

  1. 통합된 인터페이스: JobOperator는 JobRepository와 JobExplorer의 기능을 통합한 인터페이스를 제공하여, 배치 작업을 관리하는 데 있어서 한 단계 더 추상화된 접근 방식을 제공합니다.
  2. 간편한 작업 관리: JobOperator를 사용하면 복잡한 API 호출 없이 간단한 메소드 호출로 작업 실행, 중지, 재시작 등을 할 수 있어서 사용자 친화적입니다.
  3. 실시간 작업 제어: 실시간으로 배치 작업을 제어하고 관리할 수 있으며, 특히 외부 시스템에서 배치 작업을 관리할 필요가 있을 때 유용합니다.
  4. 프로그래밍적 접근: JobOperator는 프로그래밍을 통해 배치 작업을 제어할 수 있게 해주어, 관리자나 운영자가 콘솔이나 관리 도구를 통해 직접 작업을 제어할 수 있도록 합니다.

즉, JobOperator는 JobRepository와 JobExplorer의 기능을 더 높은 수준에서 통합하고 간소화하여, 배치 작업을 관리하는 과정을 보다 효율적이고 직관적으로 만들어 줍니다. 이는 복잡한 배치 프로세스를 운영하고 관리하는 데 있어서 매우 유용한 도구가 될 수 있습니다.

 

정말 gpt 너 없이 공부하던 시절 너무 힘들었다....

 

5. JobParametersIncrementer

JobOperator의 메소드의 대부분은 따로 설명할 필요가 없고 더 자세한 설명은 Javadoc of the interface에서 찾을 수 있다.

(원래 Javadoc of the interface가 링크가 있는데 Not Found 페이지로 안내한다 ㅠㅠ)

하지만, startNextInstance 메소드는 알만한 가치가 있다. 이 메소드는 항상 Job의 새로운 인스턴스를 시작한다. 이것은 JobExecution에 심각한 문제가 있을때랑 Job이 처음부터 다시 시작할 필요가 있을 때 매우 유용하다. 새로운 JobInstance를 실행하려면 새로운 jobParameter 객체들이 필요한 JobLauncher와 다르게, 매개변수가 이전 매개변수 세트와 다른 경우 startNextInstance 메소드는 Job에 연결된 JobParametersIncrementer를 사용하여 Job을 새 인스턴스로 강제 실행한다.

 

public interface JobParametersIncrementer {

    JobParameters getNext(JobParameters parameters);

}

 

JobParametersIncrementer의 역할은 주어진 JobParameter 객체로 포함하는 어떤 필요한 값을 증가시켜서 다음 JobParameter 객체로 반환하는 것이다. 이 전략은 프레임워크가 JobParameters에서  어떠한 변화가 "next" 인스턴스를 만다는지 알 방법이 없이 때문에 유용하다. 예를 들어, JobParamter의 유일한 값이 날짜이고 다음(next) instance가 생성되야 한다면 그 값은 하루 혹은 일주일이 증가되야 하는가? Job을 식별하는데 도움을 주는 어떤 숫자 값에 대해서도 마찬가지다. 다음 예시처럼 말이다.

 

public class SampleIncrementer implements JobParametersIncrementer {

    public JobParameters getNext(JobParameters parameters) {
        if (parameters==null || parameters.isEmpty()) {
            return new JobParametersBuilder().addLong("run.id", 1L).toJobParameters();
        }
        long id = parameters.getLong("run.id",1L) + 1;
        return new JobParametersBuilder().addLong("run.id", id).toJobParameters();
    }
}

 

이 예시에서, run.id의 키를 가진 값은 JobInstance사이에서 차별하기 위해 사용된다. 만약 넘겨받은 JobParameter가 null이면 Job이 전에 실행되지 않았다고 가정할 수 있다. 따라서, 그것의 초기값이 반환될 수 있다. 하지만 그렇지 않고 오래된 값이 얻어진다면, 1이 증가되고 반환된다. 

 

Java에서 정의된 job들에 대해 다음과 같이 빌더에서 제공된 incrememter 메소드를 통해 Job과 incrementer를 연동할 수 있다.

 

@Bean
public Job footballJob(JobRepository jobRepository) {
    return new JobBuilder("footballJob", jobRepository)
    				 .incrementer(sampleIncrementer())
    				 ...
                     .build();
}

 

 

6. Stopping a Job

JobOperator의 가장 흔한 유스 케이스중 하나는 우아하게(gracefully) Job을 정지하는 것이다.

 

Set<Long> executions = jobOperator.getRunningExecutions("sampleJob"); jobOperator.stop(executions.iterator().next());

 

Shuwdown은 즉각적이지 않은데 특히 실행이 현재 프레임워크가 통제하지 않는 개발자 코드에서 일어난다면 즉각적인 showdown을 강제할 방법이 없기 때문이다. 하지만, 일단 제어가 프레임워크로 반환되면, 프레임워크는 현재 StepExecution의 상태를 BatchStatus.STOPPED로 설정하고, 이를 저장한 다음, JobExecution에 대해서도 동일한 작업을 수행한 후 처리를 마친다.

 

7. Aborting a Job

실패한 job 실행은 Job의 restartable하다면 재시작할 수 있다. 상태가 ABANDONED인 job 실행은 프레임워크에의해 재시작될 수 없다. ABANDONED 상태는 또한 재시작된 job 실행에서 건너뛸 수 있도록 step executions에서 사용된다. 만약 잡이 작동중이고 이전에 실패한 job execution에서 ABANDONDED로 표시된 step을 맞닥드린다면 다음 step으로 넘어간다.

 

프로세스가 강제로 종료되었거나(예: kill -9 명령어 사용) 서버 장애가 발생했다면, 물론 작업은 실행 중이 아니지만, JobRepository는 이를 알 수 없다. 왜냐하면 프로세스가 종료되기 전에 아무도 JobRepository에게 알리지 않았기 때문이다. 작업 실행이 실패했거나 중단되었다는 것을 수동으로 JobRepository에 알려야 한다(그 상태를 FAILED 또는 ABANDONED로 변경해야 한다). 이것은 비즈니스 결정이며, 자동화할 방법은 없습니다. 재시작 가능한 경우이고 재시작 데이터가 유효하다는 것을 알고 있을 때만 상태를 FAILED로 변경해라,

 


 

이번 글은 지난 글에 이어서 Configuring and Running a Job 섹션의  JobRegistry부터 다시 읽어보았다.

 

아 그냥 그렇구나~ 하는 느낌이 좀 많이 드는데 세세한 부분을 다 전부 읽다보니 사실 감이 잘 안오긴 한다.

 

그래서 직접 코드를 치면서 해야 하는데 여기 spring batch 섹션은 뭔가 좀 그게 어렵다는 느낌이 든달까?

 

하여간 계속 열심히 읽을 예정이긴 하다.


출처:

https://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte2:brte:batch_core:job_registry

https://docs.spring.io/spring-batch/docs/5.0.4/reference/html/job.html#configureJob