취미겸생업

[Spring boot] @Scheduled 본문

IT/Spring Boot

[Spring boot] @Scheduled

nohumb 2026. 6. 23. 16:11
SMALL

Scheduler

소개

Spring Batch와 Scheduler의 차이

Spring 환경에서 일정 시간마다 특정 동작을 수행해야 할 때 스케줄러와 스프링 배치를 활용할 수 있다.
두 도구는 얼핏 보면 비슷한 목적으로 사용하는 것 같지만 엄밀히는 동작 방식과 무게에 따라 목적에 차이가 있다.

간단히 얘기해서, Scheduler는 아주 가볍고, Spring Batch는 비교적 무겁고 복잡하다.
스케줄러는 처리할 데이터의 양이 많지 않고 시스템에 큰 영향을 주지 않으며 자주 실행될 때 또는 적합하다.

Cron

@Scheduled는 Cron 문법을 사용하는데, 반복하고자 하는 시간을 표현한다. 단, 이때 5자를 사용하는 Linux의 Cron과 달리 Spring의 Cron은 6자리를 사용한다.

*  *  *  *  *  *
│  │  │  │  │  └─ 요일 (0-7: 0,7은 일요일, 1:월, 2:화...) 또는 기호(MON, TUE...)
│  │  │  │  └─── 월 (1-12) 또는 기호(JAN, FEB...)
│  │  │  └────── 일 (1-31)
│  │  └───────── 시 (0-23)
│  └──────────── 분 (0-59)
└─────────────── 초 (0-59)
  • 특수기호
    • * : 모든 항에서 사용, 모든 값을 의미 ex) 0 0 * * * * → 모든 정각마다
    • ? : 요일 필드에서만 사용하며, "조건을 지정하지 않겠다"는 뜻 (일과 요일은 서로 간섭하기 때문에 한 쪽에 지정을 하면 다른 쪽은 ?를 넣는 것이 JPA/Spring 관습)
    • / : 시작시간과 주기를 지정 (”0/5” ➔ 0초부터 시작해서 5초마다)
    • , : 특정 값들을 복수 지정, 콤마 간에 공백 없어야 함 (”1,2,3” ➔ 1시, 2시, 3시에 실행)
    • - : 범위를 지정 (”9-18” ➔ 9시부터 18시까지 매시 실행)

적용

설정

  • SchedulerConfig.java, 스프링 부트 애플리케이션에서 스케줄러 기능을 활성화하기 위해 설정 클래스에 @EnableScheduling을 선언한다.

    @Configuration
    @EnableScheduling
    public class SchedulerConfig {}

Cron 방식

Cron 문법을 사용해 정확히 지정된 시점(정각, 새벽 등)에 주 주기적으로 실행해야 하는 비즈니스 로직에 적합하다.

  • Scheduler.java

    @Component
    @RequiredArgsConstructor
    @Slf4j
    public class Scheduler {
      private final TokenRepository tokenRepository;
    
      // 매일 새벽 4시에 휴면 상태가 된 임시 토큰/캐시 데이터 일괄 삭제
      @Scheduled(cron = "0 0 4 * * *")
      public void purgeExpiredTokens() {
          log.info("휴면 토큰 청소 스케줄러 시작");
          tokenRepository.deleteByExpiredAtBefore(LocalDateTime.now());
      }
    }

fixedDelay 방식

이전 작업이 완전히 끝난 시점으로부터 일정 시간이 지난 뒤에 다음 작업을 실행하고 싶을 때 사용한다.

  • Scheduler.java

    @Component
    @RequiredArgsConstructor
    @Slf4j
    public class CachePurgeScheduler {
      private final KeywordService keywordService;
    
      // 실시간 인기 검색어 순위 반영을 위해 10분(600,000ms)마다 통계 테이블 갱신
      @Scheduled(fixedDelay = 600000)
      public void refreshPopularKeywords() {
          log.info("인기 검색어 캐시 동기화");
          keywordService.refreshCache();
      }
    }    

fixedRate 방식

앞선 작업의 종료 여부와 상관없이 시작 시점 기준으로 주기를 맞추는 방식(실시간성)이다.
만약 이전 작업이 끝나지 않았을 경우 새로운 작업이 큐에 대기하게 되며, 이전 작업이 끝나면 한꺼번에 실행된다. 이를 방지하려면 @Async를 통해 비동기로 병렬 처리 설정을 해야 한다.

스레드를 점유하거나 로직이 밀릴 가능성 때문에 실무에선 지양하는 편

  • SchedulerConfig.java, @Async 활성화 및 스레드풀 상한 명시

    @Configuration
    @EnableScheduling
    @EnableAsync
    public class SchedulerConfig {
      // 비동기 작업들이 사용할 스레드 풀 정의
      @Bean(name = "taskExecutor")
      public Executor taskExecutor() {
          ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
          executor.setCorePoolSize(5);   // 기본 스레드 개수
          executor.setMaxPoolSize(10);   // 최대 스레드 개수
          executor.setQueueCapacity(100); // 대기 큐 크기
          executor.setThreadNamePrefix("AsyncScheduler-");
          executor.initialize();
          return executor;
      }
    }
  • Scheduler.java

    @Component
    @RequiredArgsConstructor
    @Slf4j
    public class RealTimeDataScheduler {
      // 앞 작업이 3초가 걸리든 말든, 정확히 1초마다 병렬로 실행됨
      @Async
      @Scheduled(fixedRate = 1000) 
      public void collectData() {
          log.info("1초 주기 실시간 데이터 수집 시작 - 실행 스레드: {}", Thread.currentThread().getName());
          // 만약 외부 API가 느려져서 여기서 3초가 소요되더라도, 
          // 다른 스레드가 1초 뒤에 다음 collectData()를 실행해줌
      }
    }
LIST