HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
✍🏻
Learnary (learn - diary)
/
Scheduler

Scheduler

progress
Done
Tags
Spring
Spring Scheduler란?Spring Scheduler사용시점WhySpring Scheduler 체험Multi-Server 환경에서의 Spring SchedulerSpring Scheduler의 스레드REFER

Spring Scheduler란?

일정한 시간 간격 또는 일정한 시각에 특정 로직을 돌리기 위해서 사용하는 것을 Scheduler라고 한다.
일반적으로 SpringBatch와는 용도가 다르다.
SpringBatch는 일괄 처리 방식으로 대량의 데이터를 처리하기 위한 방식이다.
즉, 스케줄러보다는 큰 단위를 처리한다.
그래서 그러한 처리 방식이 또 세분화 적으로 나누어졌다.
 

Spring Scheduler사용시점

보통 고정된 가격, 지정된 시간에 작업을 예약하고 실행하는데 사용된다.
예를 들어 이메일 알림전송, 데이터베이스 레코드 업데이트 또는 정리 작업과 같은 간단한 작업에 적합하다.
 

Why

수기로 하는 것은 생산적인 측면에서 비효율적이기 때문이다.
이것은 정기적으로 처리를 하기 위한 자동화 기법으로 생산성 측면에서 좋은 장점이다.
 

Spring Scheduler 체험

별도의 추가적인 의존성이 필요하지 않고 애노테이션 설정으로 처리할 수 있다.
 
  1. 애노테이션 설정(나 스케줄러 사용한다!)
    1. notion image
  1. 스케줄러 실제 사용
 
notion image
 
 
스케줄 애노테이션 속성
notion image
이런 구조로 되어있고 보통 cron을 이용하여 처리한다.
 
fixedDelay : milliseconds 단위로, 이전 Task의 종료 시점으로부터 정의된 시간만큼 지난 후 Task를 실행한다.
fixedDelayString : fixedDelay와 같은데 문자열로 값을 표현하겠다는 의미이다.
fixedRate : milliseconds 단위로, 이전 Task의 시작 시점으로부터 정의된 시간만큼 지난 후 Task를 실행한다.
fixedRateString : fixedRate와 같은데 문자열로 값을 표현하겠다는 의미이다.
※ fixedDelay vs fixedRate
notion image
fixedRate는 작업 수행시간과 상관없이 일정 주기마다 메소드를 호출하는 것이다.
fixedDelay는 (작업 수행 시간을 포함하여) 작업을 마친 후부터 주기 타이머가 돌아 메소드를 호출하는 것이다.
initialDelay : 스케줄러에서 메소드가 등록되자마자 수행하는 것이 아닌 초기 지연시간을 설정하는 것이다.
 
@Scheduled(fixedRate = 5000, initialDelay = 3000) public void run() {     System.out.println("Hello CoCo World!"); }
이렇게 사용하면 3초의 대기시간(initialDelay) 후에 5초(fixedRate)마다 "Hello CoCo World!"를 출력하는 작업을 스케줄러가 수행해준다.
cron : cront의 표현식으로 작업을 예약하하는 것이다.
notion image
 
첫 번째 * 부터
초(0-59) 분(0-59) 시간(0-23) 일(1-31) 월(1-12) 요일(0-6) (0: 일, 1: 월, 2:화, 3:수, 4:목, 5:금, 6:토)
Spring @Scheduled cron은 6자리 설정만 허용하며 연도 설정을 할 수 없는 단점이 있다.
 
Cron 표현식 :
  • : 모든 조건(매시, 매일, 매주처럼 사용)을 의미
  • ? : 설정 값 없음 (날짜와 요일에서만 사용 가능)
  • : 범위를 지정할 때
  • , : 여러 값을 지정할 때
  • / : 증분값, 즉 초기값과 증가치 설정에 사용
  • L : 마지막 - 지정할 수 있는 범위의 마지막 값 설정 시 사용 (날짜와 요일에서만 사용 가능)
  • W : 가장 가까운 평일(weekday)을 설정할 때
    • 예) 10W
      10일이 평일 일 때 : 10일에 실행
      10일이 토요일 일 때 : 가장 가까운 평일인 금요일(9일)에 실행
      10일이 일요일 일 때 : 가장 가까운 평일인 월요일(11일)에 실행
  • # : N번째 주 특정 요일을 설정할 때 (-요일에서만 사용 가능)
    • 예) 4#2 (목요일#2째주에 실행)
Cron 사용예시
// 매일 오후 18시에 실행 @Scheduled(cron = "0 0 18 * * *") public void run() {     System.out.println("Hello CoCo World!"); } // 매달 10일,20일 14시에 실행 @Scheduled(cron = "0 0 14 10,20 * ?") public void run() {     System.out.println("Hello CoCo World!"); } // 매달 마지막날 22시에 실행 @Scheduled(cron = "0 0 22 L * ?") public void run() {     System.out.println("Hello CoCo World!"); } // 1시간 마다 실행 ex) 01:00, 02:00, 03:00 ... @Scheduled(cron = "0 0 0/1 * * *") public void run() {     System.out.println("Hello CoCo World!"); } // 매일 9시00분-9시55분, 18시00분-18시55분 사이에 5분 간격으로 실행 @Scheduled(cron = "0 0/5 9,18 * * *") public void run() {     System.out.println("Hello CoCo World!"); } // 매일 9시00분-18시55분 사이에 5분 간격으로 실행 @Scheduled(cron = "0 0/5 9-18 * * *") public void run() {     System.out.println("Hello CoCo World!"); } // 매달 1일 10시30분에 실행 @Scheduled(cron = "0 30 10 1 * *") public void run() {     System.out.println("Hello CoCo World!"); } // 매년 3월내 월-금 10시30분에 실행 @Scheduled(cron = "0 30 10 ? 3 1-5") public void run() {     System.out.println("Hello CoCo World!"); } // 매달 마지막 토요일 10시30분에 실행 @Scheduled(cron = "0 30 10 ? * 6L") public void run() {     System.out.println("Hello CoCo World!"); }
 

Multi-Server 환경에서의 Spring Scheduler

보통 실제 운영하면서 하나의 웹애플리케이션 서버로 운영하지는 않기 때문에 이점을 반드시 확인해야 한다.
우려되는 부분은 중복처리 또는 동시성 이슈가 있을 수 있다고 판단했다.
예시를 들면
데이터베이스에 아직 처리되지 않는 알림을 처리하기 위해 레코드 데이터를 동시에 접근하여 알림 서버로 보내게 되면 2번 중복발생이 될것이다.[알림 또는 메일 중복 발송]
혹은 일정 주기로 발급받은 포인트를 2달이내에 사용하지 않으면 소멸 시키는 작업을 스케줄러로 작업한다 했을때, 아직 처리되지 않는 레코드에 동시에 접근하여 포인트를 2배로 감소시키는 이슈가 있을것이다. [포인트 중복 적립 또는 소멸 처리]
 
이러한 문제를 해결하기 위해서는 ShedLock이라는 것을 사용하여 해결할 수 있다.
  1. 의존성 추가
// ShedLock Dependency implementation 'net.javacrumbs.shedlock:shedlock-spring:5.2.0' implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:5.2.0'
  1. 빈등록
notion image
  1. 테이블 생성
CREATE TABLE `shedlock` ( name varchar(64) NOT NULL COMMENT '스케줄잠금이름', lock_until timestamp(3) NULL DEFAULT NULL COMMENT '잠금기간', locked_at timestamp(3) NULL DEFAULT NULL COMMENT '잠금일시', locked_by varchar(255) DEFAULT NULL COMMENT '잠금신청자', PRIMARY KEY (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
주의사항 : 위 테이블을 절대 수동으로 삭제하면 안된다.
shedLock은 인메모리 캐시를 지니고 있기 때문에 애플리케이션이 다시 시작될 때까지 row를 재생성하지 않는다.
즉, 수동으로 삭제될 경우 lock에 대한 update 정보가 누락된다.
  1. 애노테이션 적용
notion image
notion image
 
@SchedulerLock 어노테이션 속성값들에 대해서 알아보자.
속성
내용
name
스케줄 작업의 고유 이름. ShedLock 테이블의 name 컬럼으로 기본키 역할을 하게 되므로 스케줄 작업의 고유한 이름을 입력해야 한다.
lockAtLeastFor
작업이 Lock 되어야 할 최소한의 시간을 입력한다. 짧은 작업일 경우에는 노드간의 클럭 차이로 중복 실행되는 것을 막기위해 사용한다
lockAtMostFor
작업을 진행 중인 노드(웹 애플리케이션 서버)가 소멸될 경우에도 Lock이 유지될 시간을 입력한다. 해당 시간은 실제 작업에 소요되는 시간보다 훨씬 길게 해야 한다. (입력하지 않으면 @EnableSchedulerLock의 디폴트 값으로 세팅 5s)
ShedLock 테이블 확인(웹 애플리케이션 실행 후)
보면 어떤 작업의 고유 이름과 lock_until(언제까지 유지될지), lock_at(언제 시작됬는지), locked_by(어디서 lock을 실행했는지) 에 대한 레코드가 입력된 것을 알 수있다.
notion image
 
해당 라이브러리를 기반으로 중복 실행되어 발생하는 문제를 해결할 수 있다.
 

Spring Scheduler의 스레드

스프링 스케줄러도 곧 자원(쓰레드)를 이용해서 처리할 것이다.
그러면 API를 처리하는 쓰레드와는 별개로 작동할 것인지 궁금해서 실험을 해봤다.
 
notion image
결과는 API 처리 쓰레드와는 다르지만 해당 서비스 안에서 처리되는 다른 스케줄러 처리와 동일 쓰레드로 돌려쓰고 있다.
시간이 달라서 효율적으로 하기 위해 하나로 돌려쓰는 건가? 라는 의문이 들었고 이점을 시험해보기로 했다.
notion image
그럼 시간을 한번 다시 동일하게 바꿔보겠다.
notion image
notion image
같은 스레드로 처리하고 있는것을 확인할 수 있었다.
또 궁금한 점이 생겼다.
그래서 애초에 다른 서비스에 두면 다른 스레드가 처리하는지도 궁금했다.
각 메소드를 3개의 서비스 각각에 넣어보고 실행해보았다.
notion image
그래도 결국 같은 쓰레드로 돌려쓰고 있는것을 확인할 수 있다.
notion image
 
결국 스케줄 처리가 많으면 많을 수록 어떤 한 작업이 오래걸리게 되면 뒤에것도 자동으로 뒤로 밀어지게 되면서 이슈가 발생할 수 있는 것을 알 수 있었다.
 
이 부분은 쓰레드 풀을 조정해야 겠다는 생각이 들었다.
적정 쓰레드는 해당 테스크가 겹치는 부분의 최댓값 정도만 하면 좋을 것 같다. (여분으로 +2정도 무리 없을 것 같다)
쓰레드는 다다익선이 절대 아니기 때문이다.
 
쓰레드 풀 조정
@Configuration class SchedulerConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(5); threadPoolTaskScheduler.setThreadGroupName("scheduler thread pool"); threadPoolTaskScheduler.setThreadNamePrefix("scheduler-thread-"); threadPoolTaskScheduler.initialize(); taskRegistrar.setTaskScheduler(threadPoolTaskScheduler); } }
notion image
결과적으로 다른 쓰레드를 사용하는 것을 볼 수 있다.
즉 각 테스트들이 종속에 의한 문제가 생기지 않는다.
먼저 처리되는 작업이 소요가 오래되면서 다음것들도 밀리게되는 이슈를 예방할 수 있을 것이다.
 
이제, 스레드 풀을 생성하여 각 task들을 따로 처리할 수 있어 각 작업끼리 구애받지 않게 되었다.
그러면 스레드 풀의 여유분이 있으니 매 2초마다 다른 스레드가 이것을 주기적으로 실행하겠단 기대 또한 가지고 있을 것이다.
notion image
 
하지만 스레드를 해당 task만큼 생성하여 따로 실행되기를 기대했지만 그렇지 않았다.
notion image
결과는 동기적인 처리 과정을 거쳤다.
notion image
그저 다른 스레드로 처리할 뿐 작업은 동기적으로 처리하고 있다. 1번그림은 스레드 풀 설정 이전, 2번그림은 스레드 풀 설정 후이다.
notion image
스레드 풀 설정 후내가 기대하는 바는 이런 작업을 기대했다.
notion image
 
이 부분은 비동기적인 처리가 필요하다.
  1. config 설정에 @EnableAsync 추가
notion image
  1. 작업할 테스크 메소드 위 @Async 추가
notion image
 
결과적으로 우리가 기대하는 바와 같이 2초간 정기적으로 실행됨을 확인할 수 있게된다.
notion image
 
하지만, 쓰레드라는 비싼 자원을 사용했으므로 다른 부분에서 이슈가 생길 수 있어 지속적으로 모니터링하여 확인해야 겠다.

REFER

ShedLock
lukas-krecan • Updated May 1, 2025
Guide to ShedLock with Spring | Baeldung
Learn how to use ShedLock for scheduled jobs in your Spring application
Guide to ShedLock with Spring | Baeldung
https://www.baeldung.com/shedlock-spring
Guide to ShedLock with Spring | Baeldung
[Spring Boot] @Scheduled을 이용해 일정 시간 마다 코드 실행하기
@Scheduled Spring Boot에서 @Scheduled 어노테이션을 사용하면 일정한 시간 간격으로, 혹은 특정 시간에 코드가 실행되도록 설정할 수 있다. 주기적으로 실행해야 하는 작업이 있을 때 적용해 쉽게 사용하자. @Scheduled 사용법 @Scheduled 어노테이션을 사용하기 위해 다음과 같이 Application Class에 @EnableScheduling을 추가한다. @EnableScheduling @SpringBootApplication public class SchedulerApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } 그리고 실..
[Spring Boot] @Scheduled을 이용해 일정 시간 마다 코드 실행하기
https://dev-coco.tistory.com/176
[Spring Boot] @Scheduled을 이용해 일정 시간 마다 코드 실행하기
Scheduler 적용 배경 / Spring Scheduler / 스레드 풀과 비동기
스케줄러 정기적으로 실행해야 하는 테스크가 있을 수 있다. 일정 시간 간격으로 로직을 실행하거나, 일정 스케줄러는 그런 테스크를 처리하기에 유용하게 사용할 수 있다. 예를 들면 10초마다 한 번씩 혹은 매월 1일마다 한 번씩 지정한 로직을 수행하도록 하는 것이다. 이번 프로젝트에서 매일 한번씩 정산하는 로직을 수행해야 한다. 처음에는 아래처럼 단순히 요청 처리를 위한 스레드와 별도의 스레드를 생성하고 그 안에서 무한정 시간을 비교하는 로직을 수행하려 했으나, 스프링의 스케줄러를 사용하면 직접 스레드를 관리하지 않아도 되고, 시간 비교를 효율적으로 할 수 있을 것이라는 생각에 스프링 스케줄러를 사용하게 되었다. 스프링 프레임워크에서 Quartz 라이브러리를 사용할 수 있다. 스프링 프레임워크에서 기본적으로..
Scheduler 적용 배경 / Spring Scheduler / 스레드 풀과 비동기
https://ecsimsw.tistory.com/entry/Scheduler-적용-배경과-구조-Spring-Scheduler
Scheduler 적용 배경 / Spring Scheduler / 스레드 풀과 비동기
Spring - 스케줄러 중복실행 이슈분석
Spring 프레임워크에는 linux에서 제공하는 배치(일정주기 혹은 특정시간에 자동실행)와 같은 라이브러리를 제공한다. 이를 스프링 스케줄러라고 한다. 배치프로그램은 실무에서 필수적으로 굉장히 많이 쓰이며 스프링에서는 간단하게부터 시작해서 스프링 배치 프레임워크와 연계하여 안정적인 대용량처리 프로그램을 작성할 수 있다. (스케줄러와 스프링 배치는 같은 개념이 아니다! 스케줄러로 스프링 배치를 수행한다) 스프링 스케줄러 Spring 프레임워크 (spring-boot-starter) 내에 기본적으로 포함되어있으므로 의존성 추가는 필요하지 않다. (Quarts를 사용하는 경우에는 의존성추가 필요. 하지만 이번 글에서는 다루지 않는다) 기본적인 사용방법이다. import net.javacrumbs.shedlo..
Spring - 스케줄러 중복실행 이슈분석
https://xggames.tistory.com/79
Spring - 스케줄러 중복실행 이슈분석
멀티 서버에서 스케줄 처리하기 -ShedLock
멀티 서버에서 스케줄 처리하기 -ShedLock 2020년도 당시 실무 간 구현한 것을 개인블로그로 정리한 내용입니다.
멀티 서버에서 스케줄 처리하기 -ShedLock
https://velog.io/@recordsbeat/멀티-서버에서-스케줄-처리하기-ShedLock
멀티 서버에서 스케줄 처리하기 -ShedLock