Spring Scheduler를 활용한 데이터 수집 자동화

dev.hyjang·2025년 8월 26일

Spring Boot 애플리케이션에서 주기적인 작업을 자동화하는 가장 간단하고 강력한 방법, 바로 Spring Scheduler와 @Scheduled 어노테이션을 활용하여 작업해 보았습니다. API를 호출할 때만 데이터를 수집하는 것이 아니라, 매주 또는 매일 정해진 시간에 자동으로 데이터를 수집하고 싶을 때가 있죠? 최근 제가 진행한 '주기적인 뉴스 데이터 수집' 프로젝트를 예시로 어떻게 스케줄러를 적용하는지 단계별로 보여드리겠습니다.


Spring Scheduler의 핵심 개념

Spring Scheduler는 특정 시간에 또는 주기적으로 특정 메소드를 실행하도록 예약하는 기능입니다. Spring 프레임워크에 내장되어 있어 별도의 라이브러리 추가 없이 바로 사용할 수 있습니다.

1. 스케줄링 활성화: @EnableScheduling

가장 먼저 해야 할 일은 Spring Boot 애플리케이션에 스케줄링 기능이 켜져 있음을 알려주는 것입니다. 보통 메인 애플리케이션 클래스에 @EnableScheduling 어노테이션을 추가하여 활성화합니다.

@SpringBootApplication
@EnableScheduling // 스케줄링 기능을 활성화합니다.
public class SimpleApplication {
    public static void main(String[] args) {
        SpringApplication.run(SimpleApplication.class, args);
    }
}

이 어노테이션이 없으면 @Scheduled가 붙은 메소드는 스케줄링되지 않고 일반 메소드처럼 동작합니다.

2. 작업 예약: @Scheduled

스케줄링할 메소드 위에 @Scheduled 어노테이션을 붙여줍니다. 이 어노테이션은 다양한 속성을 통해 실행 주기를 설정할 수 있습니다.

주요 속성

1. cron: 가장 유연하고 강력한 방법입니다. Cron 표현식을 사용하여 매우 구체적인 실행 시간을 예약할 수 있습니다.

  • 형식: 초 분 시 일 월 요일
  • 예시:
    • @Scheduled(cron = "0 0 0 * * MON") : 매주 월요일 자정에 실행
    • @Scheduled(cron = "0/10 * * * * ?") : 매 10초마다 실행
    • @Scheduled(cron = "0 0 12 * * ?") : 매일 정오(12시)에 실행

2.fixedRate: 이전 작업의 시작 시간을 기준으로, 정해진 시간(밀리초)마다 작업을 실행합니다.

  • 만약 작업 실행 시간이 fixedRate보다 길어지면, 이전 작업이 끝나자마자 다음 작업이 바로 시작됩니다. => 병렬로 실행되지는 않음
  • 예시: @Scheduled(fixedRate = 60000): 1분마다 실행 (이전 작업이 30초 걸렸다면, 30초 후에 다음 작업 시작)

3.fixedDelay: 이전 작업이 끝난 시간을 기준으로, 정해진 시간(밀리초) 후에 다음 작업을 실행합니다.

  • 항상 이전 작업의 완료와 다음 작업의 시작 사이에 일정한 지연 시간을 보장합니다.
  • 예시: @Scheduled(fixedDelay = 60000): 이전 작업이 끝난 후 1분 뒤에 실행 (이전 작업이 30초 걸렸다면, 1분 30초 후에 다음 작업 시작)

4.initialDelay: 스케줄링된 작업이 처음 실행되기 전의 대기 시간(밀리초)을 설정합니다. fixedRate나 fixedDelay와 함께 사용됩니다.

  • 예시: @Scheduled(initialDelay = 10000, fixedRate = 60000): 애플리케이션 시작 후 10초 뒤에 첫 실행, 그 후 1분마다 실행

스케줄링 동작 방식 (스레드)

  • 기본적으로 Spring Scheduler는 단일 스레드로 동작합니다.
  • 즉, 여러 개의 @Scheduled 메소드가 있더라도 동시에 실행되지 않고 순차적으로 실행됩니다. 만약 하나의 작업이 오래 걸리면 다른 작업들이 지연될 수 있습니다.
  • 만약 병렬 실행이 필요하거나 더 복잡한 스케줄링이 필요하다면, 별도의 TaskScheduler 빈을 등록하여 스레드 풀의 크기를 조절하는 등 세부적인 설정을 할 수 있습니다.

1. 기존 코드: 수동으로 실행해야 하는 데이터 수집

처음 작성했던 코드는 다음과 같습니다.

NewsApiController.java (수정 전)

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class NewsApiController {

    private final NewsService newsService;

    @GetMapping("/newsCollect")
    public void collectNews() throws JsonProcessingException {
        // ... 네이버 API를 통해 뉴스를 수집하는 로직 ...
        newsService.insertNews(responseBody);
    }

    @GetMapping("/news")
    public List<News> getAllNews() {
        return newsService.getAllNews();
    }
    
    // ... API 호출 관련 private 메서드들 ...
}

/api/newsCollect 라는 API 엔드포인트를 호출해야만 네이버 뉴스 API에서 '날씨' 관련 뉴스를 가져오는 collectNews() 메서드가 실행되는 구조였습니다.

이 방식은

  • 누군가 직접 API를 호출해야만 데이터가 수집되고,
  • 컨트롤러가 API 요청 처리와 스케줄링(수동)이라는 두 가지 책임을 갖게 됩니다.

이번 목표는 스프링 스케줄러를 활용하여 collectNews() 로직을 매주 월요일 00시에 자동으로 실행하는 것입니다.


2. Spring Scheduler 적용하기

1. 스케줄링 기능 활성화 (@EnableScheduling)

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling // 스케줄링 기능을 활성화합니다!
public class SimpleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SimpleApplication.class, args);
    }

}

2. 역할 분리 - 스케줄러 클래스 생성

스케줄링 관련 로직을 컨트롤러에서 분리하여 별도의 클래스로 만드는 것이 좋습니다. 이는 코드의 역할을 명확히 나누어 유지보수를 쉽게 만듭니다. scheduler 패키지를 만들고 NewsCollectScheduler 클래스를 생성한 뒤, 기존 NewsApiController에 있던 뉴스 수집 로직을 그대로 옮겨옵니다.

package com.ccp.simple.scheduler;

@Component
@RequiredArgsConstructor
public class NewsCollectScheduler {

    private final NewsService newsService;

    /**
     * 매주 월요일 00:00에 실행
     */
    @Scheduled(cron = "0 0 0 * * MON")
    public void collectNews() throws JsonProcessingException {
        // ... NewsApiController에서 가져온 뉴스 수집 로직 ...
    }

    // ... API 호출 관련 private 메서드들 ...
}

3. 작업 예약 (@Scheduled)

새로 만든 collectNews() 메서드 위에 @Scheduled 어노테이션을 추가하여 실행 주기를 설정합니다.

@Scheduled(cron = "0 0 0 * * MON")
public void collectNews() { ... }

4. 컨트롤러 리팩토링

이제 스케줄러가 뉴스 수집을 담당하므로, NewsApiController는 본연의 임무인 '수집된 뉴스 데이터를 제공하는 API' 역할만 수행하도록 코드를 정리합니다.

package com.ccp.simple.controller;

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class NewsApiController {

    private final NewsService newsService;
	
    //뉴스 수집
    @GetMapping("/news")
    public List<News> getAllNews() {
        return newsService.getAllNews();
    }
}

마무리

Spring Scheduler를 사용하면 @EnableScheduling과 @Scheduled 단 두 개의 어노테이션만으로 복잡한 주기적 작업을 손쉽게 구현할 수 있습니다.

  • 별도 라이브러리 없이 바로 사용 가능한 간단한 기능입니다.
  • 스케줄링 로직을 분리하여 코드 구조를 개선할 수 있습니다.
  • cron, fixedRate, fixedDelay 등 다양한 옵션으로 원하는 거의 모든 스케줄링 시나리오를 구현할 수 있습니다.
profile
낭만감자

0개의 댓글