[SpringBoot] Spring에서 비동기 작업과 스케줄링 -ThreadPoolTaskScheduler 편

연유라떼·2025년 8월 20일
post-thumbnail

Spring의 대표적인 비동기 작업

Spring에서는 비동기(Asynchronous) 작업을 다양한 방식으로 처리할 수 있습니다. 대표적으로 다음과 같은 Executor 구현체들이 제공됩니다.

대표적인 Executor 구현체
SimpleAsyncTaskExecutor
ThreadPoolTaskExecutor
ThreadPoolTaskScheduler
기타 ForkJoinPool 등

그 중 스케줄링에 특화된 ThreadPoolTaskScheduler를 살펴보겠습니다.

스케줄링 기능 활성화하기

스케줄링과 비동기 실행을 사용하려면 Spring에서 두 가지 애노테이션을 활성화해야 합니다.

@EnableAsync
@EnableScheduling
@SpringBootApplication
public class SchedulerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SchedulerApplication.class, args);
    }
}

@EnableAsync: 비동기 메서드 실행을 활성화
@EnableScheduling: 스케줄링 기능을 활성화

ThreadPoolTaskScheduler Bean 등록

Spring Boot는 기본적으로 taskScheduler Bean을 자동 등록해줍니다. 하지만 스레드 풀의 개수나 스레드 이름 접두사 등 커스터마이징이 필요하다면 직접 설정해주는 것이 좋습니다.

@Configuration
public class AsyncConfig {

    @Bean(name = "taskScheduler")
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(5);                      // 최대 스레드 개수
        scheduler.setThreadNamePrefix("Sched-");       // 스레드 이름 접두사
        scheduler.initialize();
        return scheduler;
    }
}

-> taskScheduler라는 이름으로 Bean을 등록하면, Spring이 @Scheduled 애노테이션 기반 작업에 이 스레드 풀을 자동으로 사용합니다.

poolSize를 제한해두면 불필요하게 많은 스레드가 생성되는 것을 방지할 수 있습니다.
( 실제 운영 환경에서는 스케줄링 작업이 많은 리소스를 차지할 수 있으므로, 스레드 풀 크기를 적절히 조정하는 것이 중요 )

Spring 기본 TaskScheduler와 ThreadPoolTaskScheduler 비교

SpringBoot의 Scheduled의 기본 TaskScheduler와 커스텀한 ThreadPoolTaskScheduler는 다릅니다

Spring Boot는 특별한 설정이 없을 때 내부적으로 ConcurrentTaskScheduler를 기본 등록하며, 구현체는 사실상 단일 스레드 기반으로 동작합니다.

하나의 스케줄링 작업이 오래 걸리면, 다른 스케줄링 작업들이 밀릴 수 있습니다.
그래서 주기적인 작업이 필요한 것은 별도의 스레드로 생성하여 작업하기 위하여 ThreadPoolTaskScheduler로 따로 스레드 관리하는 형식으로 진행됩니다

ex) ThreadPoolTaskScheduler 필요 예시

  • 스케줄링된 작업이 여러 개 있고 그 중 일부가 실행 시간이 길 때
  • 메인이 아닌 병렬로 진행하고 싶을 때
  • 스레드 이름 규칙을 정해서 로깅/모니터링을 명확히 하고 싶을 때
  • 운영 환경에서 CPU나 IO 부하를 고려해 스레드 풀을 세밀하게 조정해야 할 때(-> 이 때 setPoolSize를 통해 커스텀 )

스케줄링 구체화

스케줄링할 메서드에는 @Scheduled 애노테이션을 붙입니다.

@Slf4j
@Component
public class ScheduledJobs {

    /** 5초마다 실행 (이전 실행 종료와 상관없이 고정 주기) */
    @Scheduled(fixedRate = 5000)
    public void fixedRateJob() {
        log.info("[fixedRateJob] running on thread={}", Thread.currentThread().getName());
    }

    /** 이전 실행이 끝난 후 10초 뒤에 재실행 */
    @Scheduled(fixedDelay = 10000)
    public void fixedDelayJob() {
        log.info("[fixedDelayJob] running on thread={}", Thread.currentThread().getName());
    }

    /** 매 분 0, 20, 40초에 실행 */
    @Scheduled(cron = "0,20,40 * * * * *")
    public void cronJob() {
        log.info("[cronJob] running on thread={}", Thread.currentThread().getName());
    }
}

@Scheduled 옵션 정리

  • fixedRate: 호출 간격이 일정 (이전 실행 완료 여부와 상관없음)
  • fixedDelay: 이전 실행이 끝난 후, 지정한 시간만큼 지연된 후 다시 실행
  • initialDelay: 첫 실행 전에 지연 시간 설정
  • cron: 크론 표현식 기반으로 실행 시점 지정
    각각의 메서드들이 일정 주기를 가지고 작업을 하도록 설정하였습니다

이에 대하여 동시에 같은 스레드를 사용하지 않고 별도의 스레드들로 작업되는지를 확인해보았습니다


실행해보면 각 작업이 Sched-1, Sched-2 와 같은 이름의 별도 스레드에서 수행되는 것을 확인할 수 있습니다.

profile
일단 공부해보겠습니다..

0개의 댓글