์˜ˆ์ œ ์ฝ”๋“œ : kdevkr/spring-demo-quartz

์†Œํ”„ํŠธ์›จ์–ด ์—”์ง€๋‹ˆ์–ด์ธ ๊ฐœ๋ฐœ์ž๋Š” ๋ฐ˜๋ณต์ ์œผ๋กœ ์ •ํ•ด์ง„ ์‹œ๊ฐ„์— ์ˆ˜ํ–‰๋˜์–ด์•ผํ•  ์ž‘์—…์„ ์˜ˆ์•ฝํ•ด๋‘๋Š” ์Šค์ผ€์ค„๋ง ๊ธฐ๋Šฅ์„ ์ž์ฃผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š” ํŽธ์ด๋‹ค. ๋ฆฌ๋ˆ…์Šค ์„œ๋ฒ„์—์„œ๋Š” ๋ฐฐ์‹œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ํฌ๋ก ํƒญ(crontab)์— ๋“ฑ๋กํ•˜์—ฌ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ฉฐ ์‹œ์Šคํ…œ์„ ๊ตฌ์„ฑํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋Š” ์Šค์ผ€์ค„๋ง ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•ด์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ•„์š”ํ•œ ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜๊ฒŒ ๋œ๋‹ค. ์ฟผ์ธ  ์Šค์ผ€์ค„๋Ÿฌ๋Š” ์Šคํ”„๋ง ๋ถ€ํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋ฏ€๋กœ ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ์Šค์ผ€์ค„ ๊ธฐ๋Šฅ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์ธ๋ฉ”๋ชจ๋ฆฌ ๋…๋ฆฝ ์Šค์ผ€์ค„๋Ÿฌ

QuartzAutoConfiguration์— ์˜ํ•ด ์ž๋™์œผ๋กœ SchedulerFactoryBean๊ฐ€ ๋“ฑ๋ก๋˜๊ณ  RAMJobStore๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์ด๊ธฐ์— ๋ณ„๋‹ค๋ฅธ ์„ค์ •์—†์ด๋„ ์ฟผ์ธ  ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

spring:
  quartz:
    scheduler-name: QuartzScheduler
    job-store-type: memory
    properties:
      org.quartz.scheduler.instanceName: QuartzScheduler
      org.quartz.scheduler.instanceId: AUTO

      org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
      org.quartz.threadPool.threadCount: 100
      
      org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

JDBC ๋ถ„์‚ฐ ์Šค์ผ€์ค„๋Ÿฌ

์‹ค๋ฌด์—์„œ ์ฟผ์ธ  ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€๋งŒ ์ฟผ์ธ  ํŒ€์—์„œ ์ œ๊ณตํ•˜๋Š” JDBC ๊ธฐ๋ฐ˜์˜ ํด๋Ÿฌ์Šคํ„ฐ๋ง ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€๋Š” ์•Š๋‹ค. ๋น„์Šทํ•˜๊ฒŒ ์Šค์ผ€์ค„ ์ƒํƒœ์— ๋Œ€ํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฝ์„ ์ด์šฉํ•˜์—ฌ ๋ถ„์‚ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์Šค์ผ€์ค„์ด ์ค‘๋ณตํ•˜์—ฌ ์‹คํ–‰๋˜์ง€ ์•Š๋„๋ก ๊ตฌํ˜„๋œ ์ƒํƒœ์ด๋‹ค. ์•„๋ฌดํŠผ ์ด ๊ธ€์—์„œ๋Š” ์ฟผ์ธ ์—์„œ ์ œ๊ณตํ•˜๋Š” JDBC ํด๋Ÿฌ์Šคํ„ฐ๋ง์œผ๋กœ ์Šค์ผ€์ค„์„ ๊ด€๋ฆฌํ•ด๋ณด๋„๋ก ํ•˜์ž.

์ฟผ์ธ  ์Šค์ผ€์ค„๋Ÿฌ์šฉ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒ์„ฑ
์ฟผ์ธ  ์Šค์ผ€์ค„๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ์ƒ์„ฑ
JDBC ๊ธฐ๋ฐ˜ JobStoreTX ๊ตฌ์„ฑ

CREATE USER quartz WITH ENCRYPTED PASSWORD 'quartz123' CONNECTION LIMIT 100;
CREATE DATABASE quartz OWNER quartz;
-- Run tables_postgres.sql
spring:
  quartz:
    scheduler-name: QuartzScheduler
    job-store-type: jdbc
    properties:
      org.quartz.scheduler.instanceName: QuartzScheduler
      org.quartz.scheduler.instanceId: AUTO

      org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
      org.quartz.threadPool.threadCount: 100

      org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
      org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
      org.quartz.jobStore.dataSource: quartzDS
      org.quartz.jobStore.isClustered: true
      org.quartz.jobStore.clusterCheckinInterval: 20000

      org.quartz.dataSource.quartzDS.provider: hikaricp
      org.quartz.dataSource.quartzDS.driver: org.postgresql.Driver
      org.quartz.dataSource.quartzDS.URL: jdbc:postgresql://localhost:5432/quartz
      org.quartz.dataSource.quartzDS.user: quartz
      org.quartz.dataSource.quartzDS.password: quartz123
      org.quartz.dataSource.quartzDS.maxConnections: 10

org.quartz.dataSource.quartzDS.provider๋ฅผ hikaricp๋กœ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด c3p0 ์ปค๋„ฅ์…˜ ํ’€์„ ๊ธฐ๋ณธ์ ์œผ๋กœ ์˜์กดํ•˜๋ฏ€๋กœ ์ฃผ์˜ํ•˜์ž.

์Šค์ผ€์ค„ ์žก ๋ฐ ํŠธ๋ฆฌ๊ฑฐ ๋“ฑ๋ก

๋งŽ์€ ๋ธ”๋กœ๊ทธ ๊ธ€์—์„œ ์ฟผ์ธ  ์Šค์ผ€์ค„๋Ÿฌ๋กœ ๋™์ž‘ํ•˜๋Š” ์Šค์ผ€์ค„ ์ •๋ณด์™€ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์–ด๋ ต๊ฒŒ ์„ค๋ช…ํ•˜์ง€๋งŒ ์ƒ๊ฐ๋ณด๋‹ค ๊ฐ„๋‹จํ•˜๋‹ค. ์Šค์ผ€์ค„ ์žก์„ ๊ตฌํ˜„ํ•  ๋•Œ์— JobDetail๊ณผ Trigger๋ฅผ ํ•จ๊ป˜ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋„๋ก ์•„๋ž˜์™€ ๊ฐ™์ด ๊ด€๋ฆฌํ•˜๋ฉด ํŽธ๋ฆฌํ•˜๊ฒŒ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค.

package com.example.demo.scheduler;

import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class SampleJob extends QuartzJobBean {
    public static final String JOB_NAME = "SampleJob";
    public static final String JOB_DETAIL_NAME = JOB_NAME + "Detail";

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        log.info("{}, {}, {}", context.getTrigger().getKey().toString(), context.getJobInstance().toString(), context.getFireTime());
    }

    @Bean(JOB_DETAIL_NAME)
    public JobDetail jobDetail() {
        return JobBuilder.newJob().ofType(SampleJob.class)
                .storeDurably()
                .withIdentity("SampleJobDetail")
                .withDescription("Invoke Sample Job...")
                .build();
    }

    @Bean
    public Trigger simpleTrigger(@Qualifier(JOB_DETAIL_NAME) JobDetail job) {
        return TriggerBuilder.newTrigger().forJob(job)
                .withIdentity("SampleJobTrigger")
                .withDescription("Sample trigger with interval")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(5))
                .build();
    }

    @Bean
    public Trigger cronTrigger(@Qualifier(JOB_DETAIL_NAME) JobDetail job) {
        return TriggerBuilder.newTrigger().forJob(job)
                .withIdentity("SampleJobTrigger")
                .withDescription("Sample trigger with cron")
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
                .build();
    }
}

์Šคํ”„๋ง ์Šค์ผ€์ค„๋ง ๋ถ„์‚ฐ ๋™๊ธฐํ™”

์ฟผ์ธ  ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ๋„์ž…ํ•˜๋Š” ์ด์œ ๋Š” ์Šคํ”„๋ง์—์„œ ์ œ๊ณตํ•˜๋Š” @Scheduled๋ฅผ ํ†ตํ•œ ์Šค์ผ€์ค„๋ง ๊ธฐ๋Šฅ์€ ํด๋Ÿฌ์Šคํ„ฐ๋ง์„ ์ง€์›ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ถ„์‚ฐ๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ฟผ์ธ  ์Šค์ผ€์ค„๋Ÿฌ์™€ ๋™์ผํ•˜๊ฒŒ JDBC ๊ธฐ๋ฐ˜์œผ๋กœ ๋™๊ธฐํ™”๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ShedLock ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋™๊ธฐํ™”๋œ ์Šค์ผ€์ค„์ด ๋™์ž‘๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค. ์Šคํ”„๋ง์—์„œ ์ œ๊ณตํ•˜๋Š” ์Šค์ผ€์ค„๋ง ๊ธฐ๋Šฅ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋งˆ๋‹ค ์‹คํ–‰ํ•ด๋„ ์ƒ๊ด€์—†๋Š” ์ž‘์—…์— ๊ฐ„๋‹จํ•˜๊ฒŒ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ์ƒ๊ฐ๋˜๋ฏ€๋กœ ๋™๊ธฐํ™”๋œ ์Šค์ผ€์ค„์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋ผ๋ฉด ์ฟผ์ธ  ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ์ข‹๋‹ค๊ณ  ์ƒ๊ฐ๋œ๋‹ค.

์ฐธ๊ณ