Spring-boot-starter-quartz 설정 삽질 정리

ljk·2024년 5월 25일

trdl;

YamlPropertiesFactoryBean는 value별로
Type을 지정해서 properties를 만들어주고,
PropertiesFactoryBean는 Type을 String으로 통일한다.
그런데 quartz 내부의 SchedulerFactory의
설정 파일은, 기본적으로 전부 String으로 받은 후에
내부적으로 parsing및 Type matching을 진행한다.
그런데 이번에는 YamlPropertiesFactoryBean를
사용하고 있어서 문제가 되었다.

이게 왜 안되지?

최근 사내에서는 legacy 프로젝트의 판 올림이 한창 진행중이다.
해당 프로젝트를 진행하고 있는 팀원 분이
기존 quartz xml 설정을 java 코드로 바꾸시는 와중,
기존과는 크게 다른 것이 없는데 isClustered 속성이
적용되지 않아서 열심히 찾아보고 계셨다.


(어째서...😢)

다행히도 기존 quartz 설정 xml이 있으니,
금방 해결될 것이라 생각하였다.

바로 두 설정을 비교해보니..


(어...?)

전혀 문제가 없어보여서
좀 더 내부 quartz 로직에 문제가 있다고 생각이 들었다.



디버깅 모드를 활용하여 값을 확인해보니,
몇몇 설정은 적용되나(instanceName, tablePrefix 등..)
정작 중요한 isClustered는 적용이 되지 않았다.

set해주는 quartzProperties에서는 isClustered가
true로 되어 있었으나 로그나 생성된 scheduler를
실제로 확인해보아도, isClustered부분은 false가 저장되었다.

quartz 공식 홈페이지를 확인해 보아도,
type을 지정해서 설명을 하고 있어서 한참을 문제를 찾지 못하고 있었다.

github이슈에도 따로 올라온게 없고
블로그 글들을 봐도 현재 이슈에 해당하는 부분이 없어서,
직접 코드를 digging해보기로 하였다.

삽질 시작

일단 starter-quartz를 사용하면 SchedulerFactoryBean이 생기는데,
이름을 보아하니 해당 bean을 사용해 Scheduler를
생성하겠구나 싶었다.

그 다음을 알기위해 디버그 모드를 활용해가며
한 단계씩 들어가보니

SchedulerFactory의 구현체인
StdSchedulerFactory에서 getScheduler() 메소드를 통해
scheduler를 갖고 오는 것을 확인하였다.


이후 메소드 내부를 확인해보니
instantiate()를 통해 scheduler를 생성 하는 것을 확인하였다.


거의 다 왔다고 생각하며 해당 메서드를 들어가보니,
꽤나 긴 내부 로직이 있었다.

하지만 다행히도 이미 다른 프로젝트에서는 정상적으로 사용하고 있었으므로, 이 메소드 내부에서부터 또 한 단계씩
디버깅 하기 시작하였다.

그렇게 한 줄씩 내려가다,

요 부분에서
tProps가 이전 프로젝트와 현재 프로젝트가 다른 걸 확인 하였다.(정상에서는 isClustered:true였으나, 현재는 isClustered:"")


해당 메소드 내부를 확인하여보니,
PROP_JOB_STORE_PREFIX(org.quartz.jobStore)
가 prefix되어있으면, property값을 가져오는 로직이였다.

사실 여기까지 왔을 때도 메소드 이름에서 눈치채지 못하고
왜 이렇게 되는지 몰랐다.


왜냐면 props(schedulerfactorybean에 set한 QuartzProperties)에는 isClustered가 true(Boolean)로 제대로 들어가 있었기 때문이다.


그래서 properties내부로 좀 더 들어가서 비교를 해보니,
java.util.Properties의 getProperty()는
String에 대해서만 대응하는 것이였다.

근데 그러면 quartz docs에서는 왜 type을 지정해서
설명을 써놓은거지? 하고 확인하여 보니,

요 메소드를 통해서 Type에 맞게 set해주고 있었다.

그런데 우리는 YamlPropertiesFactoryBean를 통해
Type을 이미 지정해서 보내다보니
getProperty()에서 항상 null이 return되고,

quartz내부에서 isClustered의 기본 값인 false로
scheduler를 생성하는 것이였다.

결론

결국 해결 방법은 두가지인데,
하나는 그대로 yaml을 쓰면서 String이 아닌 값에
따옴표를 붙여서 강제로 String으로 보내거나(true -> 'true'),

아니면 properties와 PropertiesFactoryBean를 사용하는 것.

yml이 가독성이나 file size면에서 조금 더 나은 편이지만,
전혀 문제가 되는 수준이 아니니

둘 중 어떤 걸 선택해도 문제는 없을 것 같다.

오늘의 digging 끝!

또, 기회가 된다면 애초에 Type을 지정해서도 받을 수 있게
quartz에 기여를 하는것도 괜찮을 것 같다!

ps.
기존 구성은 schedulerfactorybean을 재정의 하고 있는 코드였는데,

application.yml에서

spring-quartz-properties에 기존 자바 코드로
schedulerfactorybean을 재정의 하던 내용들을 넣어주면서
적용 하는 방법도 가능하다.(이 때는YamlPropertiesFactoryBean를 쓸 때와는 다르게
따옴표 없이 그냥 작성하면 spring 내부적으로 변환해서 값을 가져간다. isClustered: 'true'가 아닌 isClustered: true로 작성하여도 정상적으로 동작한다!)

이 부분은 설정을 yml에 빼냐,
자바 코드로 빼냐의 차이이니
둘 중에서 더 필요한 방법
(yml -> 설정이 좀 더 편하나 값을 바꾸려면 was 재기동
자바 코드 -> Class파일이 하나 더 생기지만
package 이름을 통해 더 쉽게 구분, relodableMessage를 이용해 나중에 config singleton을 was 재기동 없이 수정가능)
및 프로젝트 내 설정 컨벤션을 따라 적용하면 될 것 같다.

0개의 댓글