๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
BackEnd๐ŸŒฑ/Spring

ShedLock์œผ๋กœ ๋‹ค์ค‘ ์ธ์Šคํ„ด์Šค ํ™˜๊ฒฝ์—์„œ ๋‹จ์ผ ์Šค์ผ€์ค„๋Ÿฌ ๋™์ž‘ ๋ณด์žฅํ•˜๊ธฐ

by dkswnkk 2023. 11. 24.

๊ฐœ์š”

์Šคํ”„๋ง์—์„œ ์ œ๊ณตํ•˜๋Š” @EnableScheduling ์–ด๋…ธํ…Œ์ด์…˜์€ ๊ฐ„ํŽธํ•˜๊ฒŒ ์Šค์ผ€์ค„๋ง ์ž‘์—…์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด ์ฃผ์ง€๋งŒ, ๋‹ค์ค‘ ์ธ์Šคํ„ด์Šค ํ™˜๊ฒฝ์—์„œ๋Š” ๋™์ผํ•œ ์ž‘์—…์ด ์—ฌ๋Ÿฌ ๋ฒˆ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ฌธ์ œ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„  ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ์ง€๋งŒ, ShedLock์ด๋ผ๋Š” ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์†์‰ฝ๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ShedLock์€ ์Šคํ”„๋ง ์Šค์ผ€์ค„๋ง๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋™์ผํ•œ ์Šค์ผ€์ค„๋ง ์ž‘์—…์„ ๋™์‹œ์— ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ด ์ค๋‹ˆ๋‹ค. 

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ๊นƒํ—ˆ๋ธŒ์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

ShedLock ์ดํ•ดํ•˜๊ธฐ

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

ShedLock์˜ ๋‚ด๋ถ€ ๋™์ž‘ ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  1. ์ž ๊ธˆ ํš๋“: ShedLock์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, Redis, Zookeeper ๋“ฑ์˜ ์™ธ๋ถ€ ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž ๊ธˆ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์Šค์ผ€์ค„๋ง ์ž‘์—…์ด ์‹คํ–‰๋˜๊ธฐ ์ „์— ShedLock์€ ๋จผ์ € ์™ธ๋ถ€ ์ €์žฅ์†Œ์—์„œ ํ•ด๋‹น ์ž‘์—…์˜ ์ž ๊ธˆ ์ƒํƒœ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํ•ด๋‹น ์ž‘์—…์— ๋Œ€ํ•œ ์ž ๊ธˆ์ด ์ด๋ฏธ ํš๋“๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ, ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค์—์„œ๋Š” ํ•ด๋‹น ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  2. ์ž ๊ธˆ ์œ ์ง€: ์Šค์ผ€์ค„๋ง ์ž‘์—…์ด ์‹œ์ž‘๋˜๋ฉด, ์ž ๊ธˆ์„ ํš๋“ํ•œ ์ธ์Šคํ„ด์Šค๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ, ShedLock์€ ์ž ๊ธˆ์„ ์œ ์ง€ํ•˜์—ฌ ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค์—์„œ ๋™์ผํ•œ ์ž‘์—…์„ ์ค‘๋ณต์œผ๋กœ ์‹คํ–‰ํ•˜์ง€ ๋ชปํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ž ๊ธˆ์€ ์™ธ๋ถ€ ์ €์žฅ์†Œ์— ์ €์žฅ๋˜๋ฉฐ, ์ผ์ • ์‹œ๊ฐ„ ๋™์•ˆ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.
  3. ์ž ๊ธˆ ๊ฐฑ์‹ : ์Šค์ผ€์ค„๋ง ์ž‘์—…์˜ ์‹คํ–‰ ์‹œ๊ฐ„์ด ์ž ๊ธˆ์˜ ์œ ์ง€ ์‹œ๊ฐ„๋ณด๋‹ค ๊ธธ ๊ฒฝ์šฐ, ShedLock์€ ์ž ๊ธˆ์„ ๊ฐฑ์‹ ํ•˜์—ฌ ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค์—์„œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์ง€ ๋ชปํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ž‘์—…์˜ ์ค‘๋ณต ์‹คํ–‰์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  4. ์ž ๊ธˆ ํ•ด์ œ: ์Šค์ผ€์ค„๋ง ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด, ShedLock์€ ์ž ๊ธˆ์„ ์ž๋™์œผ๋กœ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค. ์ž ๊ธˆ์€ ์™ธ๋ถ€ ์ €์žฅ์†Œ์—์„œ ์ œ๊ฑฐ๋˜์–ด ๋‹ค์Œ ์‹คํ–‰ ์ฃผ๊ธฐ์—์„œ ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

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

 

 

ShedLock ์‚ฌ์šฉํ•˜๊ธฐ

ShedLock์€ ๋‹ค์–‘ํ•œ RDBMS, NOSQL, In-Memory DB ๋“ฑ ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋Š”๋ฐ, ํ…Œ์ŠคํŠธ๋Š” MariaDB๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ™˜๊ฒฝ์—์„œ ์ง„ํ–‰ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์˜์กด์„ฑ ์ถ”๊ฐ€

Gradle์— ์•„๋ž˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

implementation 'net.javacrumbs.shedlock:shedlock-spring:5.10.0'
implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:5.10.0'

lockProvider ์ •์˜

๋‹ค์Œ์œผ๋กœ Config๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. 

@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class ScheduleConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("scheduled-task-pool-");
        scheduler.initialize();
        taskRegistrar.setTaskScheduler(scheduler);
    }

    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(
                JdbcTemplateLockProvider.Configuration.builder()
                        .withJdbcTemplate(new JdbcTemplate(dataSource))
                        .usingDbTime()
                        .build()
        );
    }
}

์œ„์˜ ์ฝ”๋“œ์—์„œ @EnableSchedulerLock(defaultLockAtMostFor = "PT30S")๋ฅผ ํ†ตํ•ด ShedLock์˜ ์Šค์ผ€์ค„๋Ÿฌ ์ž ๊ธˆ ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•˜๋ฉฐ, defaultLockAtMostFor๋Š” ๊ธฐ๋ณธ ์ž ๊ธˆ์˜ ์ตœ๋Œ€ ์œ ์ง€ ์‹œ๊ฐ„์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

lockProvider๋Š” ShedLock์ด ์ž ๊ธˆ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” bean์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” JdbcTemplateLockProvider๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ๊ด€๋ฆฌํ•˜๋Š” DataSource๋ฅผ ์ธ์ž๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ShedLock์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ด์šฉํ•˜์—ฌ ์ž ๊ธˆ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์œ„์— ์ ํžŒ ์„ค์ • ๋ง๊ณ ๋„ JdbcTemplateLockProvider์— ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋Š” ์˜ต์…˜๋“ค์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • withJdbcTemplate(JdbcTemplate jdbcTemplate): JdbcTemplate ๊ฐ์ฒด๋ฅผ ์„ค์ •.
  • usingDbTime(): ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋ฒ„์˜ ์‹œ๊ฐ„์„ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •. ์ด ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋ฒ„ ๊ฐ„์˜ ์‹œ๊ฐ„ ์ฐจ์ด๋ฅผ ๊ณ ๋ คํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
  • withLockId(String lockId): ์ž ๊ธˆ์˜ ID๋ฅผ ์„ค์ •. ๋™์ผํ•œ ID๋ฅผ ๊ฐ€์ง„ ์ž ๊ธˆ์€ ๋™์ผํ•œ ๋ ˆ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉ.
  • withColumnNames(ColumnNameProvider columnNames): ์‚ฌ์šฉํ•  ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ ์ด๋ฆ„์„ ์„ค์ •. ColumnNameProvider ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์‚ฌ์šฉ.
  • usingDbTime(ZoneId zone): ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋ฒ„์˜ ์‹œ๊ฐ„๋Œ€๋ฅผ ์„ค์ •. ์ด ์˜ต์…˜์€ usingDbTime()์™€ ๊ฐ™์ด ์‚ฌ์šฉ๋˜์ง€๋งŒ, ํŠน์ • ์‹œ๊ฐ„๋Œ€๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • withTimeZone(TimeZone timeZone): ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์‹œ๊ฐ„๋Œ€๋ฅผ ์„ค์ •.
  • withTableName(String tableName): ์ž ๊ธˆ์„ ์ €์žฅํ•  ํ…Œ์ด๋ธ” ์ด๋ฆ„์„ ์„ค์ •.
  • withIsolationLevel(IsolationLevel isolationLevel): ํŠธ๋žœ์žญ์…˜ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์„ ์„ค์ •.
  • withTransactionManager(PlatformTransactionManager transactionManager): ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ์ž๋ฅผ ์„ค์ •.

@SchedulerLock ์„ค์ •

@Slf4j
@Component
public class Scheduling {
    @Scheduled(cron = "*/5 * * * * *")
    @SchedulerLock(name = "scheduled1", lockAtMostFor = "PT2S", lockAtLeastFor = "PT2S")
    public void schedule2() {
        log.info("Scheduled task executed at {}", LocalDateTime.now());
    }
    
    @Scheduled(cron = "*/5 * * * * *")
    @SchedulerLock(name = "scheduled2", lockAtMostFor = "PT2S", lockAtLeastFor = "PT2S")
    public void schedule2() {
        log.info("Scheduled task executed at {}", LocalDateTime.now());
    }
    
    @Scheduled(cron = "*/5 * * * * *")
    @SchedulerLock(name = "scheduled3", lockAtMostFor = "PT2S", lockAtLeastFor = "PT2S")
    public void schedule3() {
        log.info("Scheduled task executed at {}", LocalDateTime.now());
    }
}

์œ„์˜ ์ฝ”๋“œ๋Š” @Scheduled์™€ @SchedulerLock ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์Šค์ผ€์ค„๋ง ์ž‘์—…์„ ์ •์˜ํ•œ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. @SchedulerLock ์–ด๋…ธํ…Œ์ด์…˜์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์†์„ฑ๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • name (ํ•„์ˆ˜): ์ž ๊ธˆ์˜ ์ด๋ฆ„์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ™์€ ์ด๋ฆ„์„ ๊ฐ€์ง„ ์ž ๊ธˆ์€ ๋™์ผํ•œ ๋ ˆ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, ๊ณ ์œ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • lockAtMostFor (ํ•„์ˆ˜): ์ž ๊ธˆ์ด ์ตœ๋Œ€๋กœ ์œ ์ง€๋˜๋Š” ์‹œ๊ฐ„์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด ์‹œ๊ฐ„ ์ดํ›„์—๋Š” ์ž ๊ธˆ์ด ์ž๋™์œผ๋กœ ํ•ด์ œ๋˜๋ฉฐ, ์ด ์†์„ฑ์€ 'PT30S', '10m', '2h' ๋“ฑ์˜ ISO 8601 ํ˜•์‹์˜ ๋ฌธ์ž์—ด์„ ์‚ฌ์šฉํ•˜์—ฌ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ’์€ ๋ฐ˜๋“œ์‹œ ์ž‘์—…์˜ ์˜ˆ์ƒ ์ตœ๋Œ€ ์‹คํ–‰ ์‹œ๊ฐ„๋ณด๋‹ค ์ปค์•ผ ํ•˜๋Š”๋ฐ, ๋งŒ์•ฝ ์ž‘์—…์ด ์ด ์‹œ๊ฐ„์„ ์ดˆ๊ณผํ•˜์—ฌ ์‹คํ–‰๋œ๋‹ค๋ฉด, ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค์—์„œ ์ž ๊ธˆ์„ ํš๋“ํ•˜๊ณ  ๋™์ผํ•œ ์ž‘์—…์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • lockAtLeastFor(์„ ํƒ): ์ž ๊ธˆ์ด ์ตœ์†Œ๋กœ ์œ ์ง€๋˜๋Š” ์‹œ๊ฐ„์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ž ๊ธˆ์ด ํš๋“๋œ ํ›„, ์ด ์‹œ๊ฐ„ ์ด๋‚ด์—๋Š” ์ž ๊ธˆ์ด ํ•ด์ œ๋˜์ง€ ์•Š์œผ๋ฉฐ, ์ด ์†์„ฑ ๋˜ํ•œ ISO 8601 ํ˜•์‹์˜ ๋ฌธ์ž์—ด์„ ์‚ฌ์šฉํ•˜์—ฌ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฐ’์„ ์„ค์ •ํ•˜๋ฉด, ์ž‘์—…์ด ๋น ๋ฅด๊ฒŒ ์ˆ˜ํ–‰๋œ ํ›„์—๋„ ์ผ์ • ์‹œ๊ฐ„ ๋™์•ˆ์€ ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค์—์„œ ๋™์ผํ•œ ์ž‘์—…์ด ์‹คํ–‰๋˜์ง€ ์•Š๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • mode(์„ ํƒ): ์ž ๊ธˆ ๋ชจ๋“œ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ 'IMMEDIATE'๋กœ, ์ด ๋ชจ๋“œ์—์„œ๋Š” ์ž ๊ธˆ์„ ์ฆ‰์‹œ ํš๋“ํ•˜๋ ค๊ณ  ์‹œ๋„ํ•˜๋Š”๋ฐ, ๋งŒ์•ฝ ์ž ๊ธˆ์„ ํš๋“ํ•˜์ง€ ๋ชปํ•˜๋ฉด ์ž‘์—…์€ ์ฆ‰์‹œ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์˜ต์…˜์œผ๋กœ๋Š” 'EAGER'๊ฐ€ ์žˆ์œผ๋ฉฐ, ์ด ๋ชจ๋“œ์—์„œ๋Š” ์ž ๊ธˆ์„ ํš๋“ํ•˜๊ธฐ ์œ„ํ•ด ์ง€์ •๋œ ์‹œ๊ฐ„ ๋™์•ˆ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค.

ShedLock ํ…Œ์ด๋ธ” ์ƒ์„ฑ

-- MySQL, MariaDB
CREATE TABLE shedlock
(
    name       VARCHAR(64)  NOT NULL,
    lock_until TIMESTAMP(3) NOT NULL,
    locked_at  TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    locked_by  VARCHAR(255) NOT NULL,
    PRIMARY KEY (name)
);

-- Postgres
CREATE TABLE shedlock
(
    name       VARCHAR(64)  NOT NULL,
    lock_until TIMESTAMP    NOT NULL,
    locked_at  TIMESTAMP    NOT NULL,
    locked_by  VARCHAR(255) NOT NULL,
    PRIMARY KEY (name)
);

-- Oracle
CREATE TABLE shedlock
(
    name       VARCHAR(64)  NOT NULL,
    lock_until TIMESTAMP(3) NOT NULL,
    locked_at  TIMESTAMP(3) NOT NULL,
    locked_by  VARCHAR(255) NOT NULL,
    PRIMARY KEY (name)
);

-- MS SQL
CREATE TABLE shedlock
(
    name      VARCHAR(64)  NOT NULL,
    lock_until datetime2 NOT NULL,
    locked_at datetime2 NOT NULL,
    locked_by VARCHAR(255) NOT NULL,
    PRIMARY KEY (name)
);

-- DB2
CREATE TABLE shedlock
(
    name       VARCHAR(64)  NOT NULL PRIMARY KEY,
    lock_until TIMESTAMP    NOT NULL,
    locked_at  TIMESTAMP    NOT NULL,
    locked_by  VARCHAR(255) NOT NULL
);

shedlock ๋ฐ์ดํ„ฐ ํ™•์ธ

์‹ค์ œ shedlock์˜ ๋ฐ์ดํ„ฐ๋Š” ์œ„์™€ ๊ฐ™์ด ์ €์žฅ๋˜๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ์‚ฌ์‹ค์€ ShedLock์€ ์Šค์ผ€์ค„๋ง ์ž‘์—…์ด ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ๋ ˆ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, ํ•ด๋‹น ์ž‘์—…์˜ ์ž ๊ธˆ ๋ ˆ์ฝ”๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ถ€ํ•˜๋ฅผ ์ค„์ด๋Š” ๋ฐ ๋„์›€์ด ๋˜๋ฉฐ, ๋™์‹œ์— ์ˆ˜ํ–‰๋˜๋Š” ์ž‘์—…์„ ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.(๋”ฐ๋ผ์„œ ์ž‘์—…์˜ ์‹คํ–‰ ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•˜๋ ค๋ฉด ๋ณ„๋„๋กœ ๋ณ„๋„์˜ ๋กœ๊น… ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๊ฑฐ๋‚˜, ์Šค์ผ€์ค„๋ง ์ž‘์—… ๋‚ด์—์„œ ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.)

 

 

์—๋Ÿฌ ํ•ด๊ฒฐ ๊ณผ์ •

์—ฌ๊ธฐ๋ถ€ํ„ฐ๋Š” ์ œ๊ฐ€ ShedLock์„ ์ ์šฉํ•˜๋ฉด์„œ ๊ฒช์—ˆ๋˜ ์—๋Ÿฌ์— ๋Œ€ํ•ด ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

1. ์˜์กด์„ฑ ๋ฒ„์ „ ์—๋Ÿฌ

๋ฒ„์ „ ์—๋Ÿฌ

ํ”„๋กœ์ ํŠธ์˜ JDK ๋ฒ„์ „๊ณผ ShedLock ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋นŒ๋“œ๋œ JDK ๋ฒ„์ „์ด ์ผ์น˜ํ•˜์ง€ ์•Š์•„ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, ํ”„๋กœ์ ํŠธ์˜ JDK ๋ฒ„์ „์— ๋งž๋Š” ShedLock ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, JDK 17 ๋ฒ„์ „ ์ด์ „์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ShedLock ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ v4.44.0์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ •๋ณด๋Š” ShedLock์˜ README์˜ VERSION ๋ชฉ์ฐจ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

JDK ๋ฒ„์ „์— ๋”ฐ๋ฅธ ์˜์กด์„ฑ ๋ฒ„์ „ ํ™•์ธ

๊นƒํ—ˆ๋ธŒ๋ฅผ ๋ณด๋ฉด JDK 17 ๋ฒ„์ „ ์ด์ „์€ v4.44.0, ์ดํ›„๋Š” v5.1.0์„ ์จ์•ผ ํ•œ๋‹ค๊ณ  ๋ช…์‹œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

2. shedlock ํ…Œ์ด๋ธ” ์ฐพ์„ ์ˆ˜ ์—†์Œ

ERROR 14:15:00 [scheduled-task-1] n.j.s.p.j.JdbcTemplateStorageAccessor, insertRecord at line 73; Unexpected exception
org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [INSERT IGNORE INTO shedlock(name, lock_until, locked_at, locked_by) VALUES(?, TIMESTAMPADD(MICROSECOND, ?, UTC_TIMESTAMP(3)), UTC_TIMESTAMP(3), ?)]; SQL state [3D000]; error code [1046]; No database selected; nested exception is java.sql.SQLException: No database selected
	at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1542)
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:667)
	at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:960)
	at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:981)
...

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ shedlock ํ…Œ์ด๋ธ”์„ ์ฐพ์ง€ ๋ชปํ•ด ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, DataSource ์ดˆ๊ธฐํ™” ์‹œ์— ์Šคํ‚ค๋งˆ๋ฅผ ์ง€์ •ํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, jdbc:mysql://localhost:3306/my_schema์™€ ๊ฐ™์ด ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

ํ•˜์ง€๋งŒ, ๋” ๊น”๋”ํ•œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ShedLock ์„ค์ •์—์„œ withTableName๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํ‚ค๋งˆ์™€ ํ…Œ์ด๋ธ” ์ด๋ฆ„์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.(๊ณต์‹๋ฌธ์„œ์˜ Configure LockProvider ๋ถ€๋ถ„์— ์ž์„ธํžˆ ๋‚˜์™€์žˆ์Šต๋‹ˆ๋‹ค.)

    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(
                JdbcTemplateLockProvider.Configuration.builder()
                        .withTableName("schema.shedlock")
                        .withJdbcTemplate(new JdbcTemplate(dataSource))
                        .usingDbTime()
                        .build()
        );
    }

์œ„ ์„ค์ •์—์„œ "schema.shedlock"์€ {์Šคํ‚ค๋งˆ}.{ํ…Œ์ด๋ธ”๋ช…}์˜ ํ˜•ํƒœ๋กœ ์ง€์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด, ํŠน์ • ์Šคํ‚ค๋งˆ ๋‚ด์˜ shedlock ํ…Œ์ด๋ธ”์„ ์ •ํ™•ํžˆ ์ฐพ์•„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋Œ“๊ธ€