2022년 11월 9일

코딩 - Job Scheduling in Spring with Quartz - Database Clustering

 Spring 프레임워크 기반의 웹 프로그램에서 시간 주기에 따라 작업을 수행하는 것은 유명 오픈소스  Quartz 을 사용하여 손쉽게 구현할 수 있다. Spring 프레임워크 역시 추상화를 통하여 Quartz 스케줄러를 위한 인스턴스 생성을 위한 FactoryBean 및 (MethodInvokingFactoryBean을 사용하여) 컨텍스트에 존재하는 Bean 함수 호출을 할 수 있게 지원하고 있다.

안정성을 중요시하는 요즘에 서버 이중화는 그리 특별한 것이 아니며 하나 이상의 프로그램 서버에 스케줄러 기반의 배치 작업을 포함하는 웹 프로그램의 경우 해결해야 하는 몇가지 이슈가 있다.

  1. 서버 인스턴스 A, B 가 있는 경우 오직 인스턴스 A 에 스케줄러 설정 포함하여 운영하는 방식을 많이 사용한다. 이경우 인스턴스 A 가 다운된 상태의 경우 스케줄러 작업이 진행되지 않는 이슈가 있음. 
  2. Quartz Jdbc Clustering 의 경우 스케줄러 작업에서 스프링에 등록된 서비스를 호출하여 작업을 하는 경우 이부분에 대한 처리가 잘 되지 않음. (구현 방법이 명확하게 기술된 자료를 확인하지 못함)

이러한 어려움들은 몇가지 문서들을 기초로하여 해결 할 수 있었다. 참고로 이글에서는 어노테이션이 아닌 XML 을 이용한 설정으로 구현하였다. Spring 프레임워크 기반에서 Quartz 스케줄러를 이용한 작업은 아래 3가지를 정의하여 구현할 수 있다.


Quartz Scheduler 

❶ 특정 Job 을 정의하는 JobDetail 정의

Quartz 스케줄러에서 실행될 작업을 담는 Job 정의하는 JobDetail 인스턴스는 JobBuilder API를 사용하면 쉽게 생성할 수 있다. Spring 에서는 ⑴ 등록된 다른 Bean 의 함수를 호출하는 작업을 위한 MethodInvokingJobDetailFactoryBean  클래스와 ⑵  실행되는 작업은 반듯이 구현해야하는 인터페이스   org.quartz.Job  을 구현하는 JobDetail 인스턴스 생성을 돕는 JobDetailFactoryBean 클래스가 있다. 

❷ Trigger 정의

스케줄러를 어떤 방식, 어떤 주기로 작동할 지를 정의한다. Trigger 은 ⑴ start time, end time, interval time, repeat times 주기를 설정하는  SimpleTrigger  과 ⑵ Cron 형식으로 일정 주기를 지정하는  CronTrigger  가  있다.

❸ Quartz Scheduler 정의 

❶ 과 ❷ 입력하여 작업을 실행한다.

그림1. Quartz Scheduler 구성요소

그림2. Quartz Scheduler 내부 구조 

Quartz 스케줄러 스레드 실행 방법을 결정하는 스레드 실행기 같이 작업 실행을 구성하고 용이하게 하는 데 도움이 되는 더  많은 구성요소들이 있다. 작업이 실행을 위해 위임된 스레드 풀을 관리하는 스레드 풀, 작업 저장소(하나는 메모리 기반이고 다른 하나는 데이터베이스 기반). 참고로 동일한 작업을 가르키는 여러 트리거가 존재할 수 있지만 하나의 트리거는 하나의 작업만 가리킬 수 있다.

테스트는 다음 환경에서 진행되었다.

테스트 환경

  • Springframework 5.3.6
  • Quartz 2.3.2 
  • Apache Tomcat 8.5.57
  • JAVA 1.8 (인프라 호환성 유지를 위하여 낮은 버전을 사용)
  •  HW : Macbook Pro 2020 ( 2.3GHz 8Core Intel Core i9  16GB Memory) 


JDBC 을 이용한 Clustering 과정에서 경험한 것들

1. Schema 생성하기

데이터베이스 스키마는 직접 생성해야 하는 데 버전에 맞는 스키마를 사용해야 한다. 다른 버전의 스키마를 이용하여 테이블을 생성하는 경우 역직렬화 오류가 발생한다.

2. Job Detail 설정

Quartz 2.1.7 버전의 경우 JobDetail 의 JobDataMap 값이 Job 구현 클래스에 자동으로 바인딩되지 않아 Job 구현 클래스에서 executeInternal() 구현시 인자로 전달되는 JobExecutionContext 에서 JobDataMap 데이터를 꺼내어 설정하는 작업이 필요했다. Quartz 2.3.2 버전에서는 자동으로 바인인되었다. 주의할 것은 JobDetail 객체를 JobDetailFactoryBean 사용하여 정의할 때 applicationContextJobDataKey 값을 설정하게 되면 Spring Context 객체가 해당 이름으로 JobDataMap 에 추가되고 결과적으로 데이터베이스  Job Store 에 저장을 시도하면서 오류를 발생시키게 된다. 


3. Quartz 설정

applicationContextSchedulerContextKey 값을 applicationContext로 지정하고 Job 구현 클래스에서 같은 이름으로 setApplicationContext 함수를 정의하면 자동으로 바인딩된다. 이를 통하여 Job 구현 클래스에서 Spring Context 에 접근이 가능하게 할 수 있다.

 

org.quartz.jobStore.useProperties 속성을 true 로 설정하면 JobDataMaps 데이터는 객체가 아닌 String 형태로 저장된다. 이경우는 JobDataMap 에 객체 값을 추가하는 것이 불가능하게 된다.


org.quartz.JobPersistenceException: Couldn't store job: JobDataMap values must be Strings when the 'useProperties' property is set.  Key of offending value: applicationContext



다음은 위의 과정들을 통하여 적용된 내용이다.


동일 이름의 Quartz 스케줄러 인스턴스가 하나 이상인 경우에 최초 활성화된 인스턴스가 동작하게 되고 다른 인스턴스들은 대기하게 된다. (로그를 보면 추정하건데 데이터베이스 컬럼 Lock 에 의존하여 클러스터링을 구현하는 것으로 보여진다.)
그림3. 대기하는 Quartz Scheduler 인스턴스 로그

참고자료

  1. Quartz Scheduler Model
  2. Scheduling in Spring with Quartz
  3. Quartz Configuration Reference - Configure Clustering with JDBC-JobStore
  4. pass JobDataMap in the JobDetailFactoryBean not working
  5. Issue with Quartz persistent jobs while using with Spring
  6. One thought on “Configuring Quartz 2 with Spring in clustered mode”

댓글 없음:

댓글 쓰기