본문 바로가기
Develop Study/Spring 2025. 9. 2.

(25.09.02) Java Spring 의 스케쥴러 @scheduled의 shedlock

최근 모 회사의 Platform Backend Engineer로 8월 11일 부터 개발자로 시작하게 되었다.

Java Spring  기반의 백엔드 서비스  Platform의 개발을 담당하면서, 서버단에서의 자동화를 위한 Scheduler가 존재하고,

대용량 DB 처리를 위해 미들웨어 또는 DB단에서가 아닌 Scheduler단에서 동시성을 제어할 수 있도록 shedlock 을 걸어줘야 했다.

 

분산 인스턴스에 적합한 Scheduler 대상으로 하는 shedlock 이기 때문에 이에 대해서 간단하게 찾아보면서 예시도 스스로 작성하면서 기록을 하고자 했다. 


ShedLock

  • DB 등 분산 환경에서 @Scheduled 즉, Scheduler 등의 스케쥴 작업이 동시에 중복 실행되지 않도록 / 분산 DB 에서 중복해서 각각 실행되지 않도록 공용 저장소 (DB/Redis 등) 에 Lock 을 기록해 단일 실행 및 데이터의 무결성을 보장 Java Library
  • 여러 인스턴스 일시 사용 사용 가능하지만, DB단의 정합성 Consistency을 유지하면서 한번의 작업에 하나의 인스턴스가 할당될수 있도록 하는 것

작동 keywords

@Component
public class SampleJob {

    // 매 분 실행되지만 여러 인스턴스 중 1개만 수행
		@Scheduled(initialDelay = 5000, fixedRate = 30000)
    @SchedulerLock(
        name = "sampleJob",
        lockAtMostForString = "PT30S",   // 최대 30초 잠금
        lockAtLeastForString = "PT10S"   // 최소 10초 잠금
    )
    public void runJob() {
        System.out.println("실행중: " + Thread.currentThread().getName());
        // 이하 1작업 로직
    }
}
  • @Scheduled
    • cron 형식을 입력이 가능
    • initalDelay(앱 시작 후 첫 실행가지 기다리는 ms ), fixedRate(일정 간격으로 이전 작업 실행하는 ms)
  • @SchedulerLock
    • Annotation을 통해서 동작
  • LockProvider
    • 잠금 정보를 저장 / 조회하는 BE 단
    • JDBC, Mongo, Redis, Elasticsearch 등등
  • lockAtMostFor
    • 노드가 죽어도 최대 시간 후 잠금이 자동 해제 → Deadlock 방지
    • 되도록 반드시 반드시 써줄 수 있도록 함
  • lockAtLeastFor
    • 작업이 너무 자주 돌지 않게 최소의 유지시간을 강제

shedlock Table

컬럼이름 설명 생성 시점 / 값이 들어가는 시점 용도
name 잠금 이름 (PK) 테이블 생성 시, 각 스케줄러 작업 이름에 맞춰 수동 입력 잠금 식별자. 각 작업마다 고유해야 함. PK 역할
lock_until 잠금 만료 시간 잠금을 획득할 때 현재 시간 + lockAtMostFor로 계산하여 기록 잠금 만료 시간. 이 시간 이후엔 다른 인스턴스가 잠금 획득 가능
locked_at 잠금이 획득된 시간 잠금을 획득할 때 현재 시간 기록 언제 잠금을 잡았는지 확인용
       
locked_by 어떤 인스턴스가 잠금을 잡았는지 잠금을 획득할 때 인스턴스 식별자 기록 어떤 서버/노드가 잠금을 가지고 있는지 확인
  • 테이블과 위의 컬럼은 반드시 생성이 되어야 shedlock이 scheduled와 작동할 수 있음
    • JDBC일 경우 Provider 를 통해서 바로 테이블 명은 커스텀 가능 단, 테이블의 이름은 shedlock 자체를 쓰는 것을 권장

Spring Boot 에서 적용

// 의존성 (예시)
implementation("net.javacrumbs.shedlock:shedlock-spring:6.10.0")
implementation("net.javacrumbs.shedlock:shedlock-provider-jdbc-template:6.10.0")
  • 라이브러리 자체를 의존성 주입을 해 Autowired로 생성해야함
@Bean
public LockProvider lockProvider(DataSource dataSource) {
    return new JdbcTemplateLockProvider(dataSource);
}
  • ShedLock 코어 라이브럴에서 LockProvider을 제공하고 있기 때문에 Bean 등록만 해주면 가능

ShedLock + 분산 DB 환경 시나리오에 적용

  • 애플리케이션 인스턴스 A, B, C 가정
  • DB는 단일 인스턴스가 아니라 분산 DB 클러스터 가정
    • (예: MySQL Cluster, PostgreSQL with Patroni, Altibase, CockroachDB 등)
  • 이 DB는 노드 간 동기화/합의(Replication, Consensus, Quorum) 로 데이터 정합성을 유지하고 있음
  • shedlock 테이블은 클러스터 내에서 논리적으로 하나만 존재.

2. 실행 흐름 (분산 환경)

  1. 스케줄러 트리거
    • 인스턴스 A, B, C가 동시에 @Scheduled + @SchedulerLock(name="jobX") 메서드를 실행하려고 함.
  2. LockProvider → 분산 DB에 Lock 획득 시도
    • 각 인스턴스는 shedlock 테이블을 UPDATE/INSERT 하려 시도
  3. 분산 DB의 합의 과정 (내부작동)
    • DB 클러스터 내부에서 트랜잭션이 합의
    • 모든 노드가 동기화된 후 단 한 개의 트랜잭션만 성공하도록 보장
    • 나머지는 Lock 조건 불일치로 실패
  4. 선점 성공/실패
    • 인스턴스 A의 쿼리가 먼저 합의 완료 → 잠금 획득
    • 인스턴스 B, C는 UPDATE 실패 → "Lock을 잡지 못했다"로 처리
  5. 작업 실행
    • A만 실제 작업 실행
    • B, C는 그냥 스킵
  6. 작업 종료/타임아웃
    • A가 정상 종료 → **lock_until**을 **now()**로 업데이트 → 잠금 해제
    • 만약 A가 비정상 종료 → lockAtMostFor 값까지 기다렸다가 잠금 자동 해제
  7. 다음 주기
    • 다시 A, B, C가 동시에 시도
    • DB 분산 트랜잭션에서 동일한 절차 반복
  • 분산 DB가 합의(Consensus)로 단일 논리적 테이블을 유지하기 때문에 ShedLock은 “중앙 DB처럼” 동작할 수 있음
  • 잠금 정보(shedlock row)는 모든 노드에 동기화 → 어떤 인스턴스가 어느 노드에 연결되든 동일하게 Lock 보장

온보딩을 진행하면서 보안 교육, HTML JavaScript 교육 등이 진행되면서 블로그 글이 조금 뜸했지만, 

이제 공부하면서 조금씩 진행할 계획이다.

+

정보처리기사 필기 시험에 이어 실기 시험을 11월에 진행할게획이다.

필기시험은 이론위지지만, 실기시험은 직접 코드를 작성하는 거도 많고 직접 코드를 알고 써야하는 것인 만큼

새로 배운 내용이 있다면 이 정보처리기사 자격증 실기 학습 관련해서도 작성할 계획이다.