자바 백엔드 입문 55편 — @Scheduled로 작업 스케줄링

2026-05-16자바 백엔드 입문

자바 백엔드 입문 55편. Phase 8 Integration 시작. @Scheduled 한 줄로 매분·매시간·매일 자동 실행되는 배치 작업을 출퇴근 자명종 비유로 풀어쓴 학습 노트.

📚 자바 백엔드 입문 · 55편 — @Scheduled로 작업 스케줄링

이 글은 자바 백엔드 입문 시리즈 59편 중 55편이에요. 52~53편 테스팅 까지 다뤘으니, 이번 55편은 운영 백엔드의 반복 작업 — "매분·매시간·매일 자동 실행되는 백엔드 작업" 의 표준 패턴을 풀어 가요.

@Scheduled — 한 줄로 끝나는 스케줄링

매일 새벽 3시에 통계 집계, 매분 헬스체크, 매 30초 큐 폴링 같은 "반복 실행" 작업이 백엔드에 흔해요. @Scheduled 한 줄로 끝.

@Component
@RequiredArgsConstructor
public class ReportScheduler {

    private final ReportService reportService;

    @Scheduled(cron = "0 0 3 * * *")           // 매일 새벽 3시
    public void generateDailyReport() {
        reportService.generate();
    }

    @Scheduled(fixedRate = 60000)              // 60초마다
    public void healthCheck() {
        log.info("Health check at {}", LocalDateTime.now());
    }
}

스케줄링 활성화는 @EnableScheduling 한 줄 — 보통 메인 클래스에.

@SpringBootApplication
@EnableScheduling
public class MyApp { ... }

Spring이 시작 시 @Scheduled 박힌 메서드를 찾아 등록 + 별도 스레드에서 주기적 실행.

3가지 스케줄링 방식

옵션 의미 사용
fixedRate 직전 실행 시작 시점 기준 N ms 후 일정한 간격 (시간 정밀)
fixedDelay 직전 실행 종료 시점 기준 N ms 후 직전 작업 완료 후 안정
cron Cron 표현식 (특정 시각) 매일·매주·매월 정해진 시각
@Scheduled(fixedRate = 60000)              // 60초마다
@Scheduled(fixedDelay = 60000)             // 직전 종료 + 60초
@Scheduled(cron = "0 0 3 * * *")           // 매일 03:00
@Scheduled(cron = "0 0/30 9-18 * * MON-FRI")   // 평일 9~18시, 30분마다
@Scheduled(cron = "0 0 1 1 * *")           // 매월 1일 새벽 1시

가장 자주 만나는 = cron. Linux cron과 거의 같은 문법.

Cron 표현식 — 6자리

Spring @Scheduled cron은 6자리 (초·분·시·일·월·요일).

초  분  시  일  월  요일
0   0   3   *   *   *      → 매일 03:00:00
0   */5 *   *   *   *      → 5분마다
0   0   9   *   *   MON    → 매주 월요일 09:00
0   0   0   1   *   *      → 매월 1일 자정

특수 문자:

문자 의미
* 모든 값
? 지정 안 함 (일·요일 충돌 회피)
/ 단위 (0/5 = 0부터 5단위)
- 범위 (MON-FRI)
, 나열 (MON,WED,FRI)

자동 생성기는 검색해서 "cron 표현식 생성기" 활용. 매번 손으로 짜는 거 어려워요.

fixedRate vs fixedDelay 차이

자주 헷갈리는 두 옵션.

fixedRate = 1000 (1초)
시점:    0 ──── 1 ──── 2 ──── 3 ──── 4
실행:   [a]    [b]    [c]    [d]    [e]
(a가 1.5초 걸리면 b는 a 끝과 동시에 시작 — 누적 가능)

fixedDelay = 1000 (1초)
시점:    0 ──── 1.5 ──── 3 ──── 4.5
실행:   [a]    [b]      [c]    [d]
(a가 1.5초 걸려도 b는 a 끝난 후 1초 대기)
  • fixedRate = "시간을 정확히 맞추기" (조금 빠르거나 누적 가능)
  • fixedDelay = "직전 작업이 끝나야 다음 시작" (안정)

길게 도는 작업은 fixedDelay 가 안전. 짧은 헬스체크 같은 건 fixedRate.

initialDelay — 시작 지연

서버 시동 직후 모든 스케줄러가 한꺼번에 도는 걸 막을 때.

@Scheduled(fixedRate = 60000, initialDelay = 30000)
public void monitor() { ... }
// 시작 30초 후 첫 실행, 그 후 60초마다

비동기 실행 — @Async

@Scheduled 는 기본적으로 단일 스레드에서 순차 실행. 여러 스케줄러가 동시에 도는 게 필요하면 @Async 조합.

@SpringBootApplication
@EnableScheduling
@EnableAsync                                  // ← 비동기 활성화
public class MyApp { ... }

@Component
public class Tasks {

    @Async
    @Scheduled(fixedRate = 60000)
    public void heavyTask() {
        // 별도 스레드에서 비동기 실행
    }
}

긴 작업이 다른 스케줄을 막지 않게.

분산 환경 함정 — 여러 서버에서 동시 실행

@Scheduled 가장 큰 함정 — 여러 인스턴스를 띄우면 각자 따로 실행. 새벽 3시 통계 집계가 — 서버 3대면 3번 동시 실행. DB 부담·중복 데이터·동시성 폭발.

해결법:

1. 한 인스턴스만 활성화 (간단)

@Scheduled(cron = "...")
@ConditionalOnProperty(name = "scheduler.enabled", havingValue = "true")
public void task() { ... }

여러 인스턴스 중 하나만 scheduler.enabled=true 박아 실행. 단순하지만 "그 인스턴스가 죽으면 스케줄 안 돔".

2. ShedLock — 분산 락

@SchedulerLock(name = "dailyReport", lockAtMostFor = "10m")
@Scheduled(cron = "0 0 3 * * *")
public void generateDailyReport() { ... }

ShedLock 라이브러리가 DB에 락을 박아 — 어느 한 인스턴스만 실행 보장. 한국 회사 시스템 표준 패턴.

3. 별도 배치 서버 분리

규모 큰 시스템은 — 웹 API 서버와 배치 서버를 분리. 배치 서버에만 @EnableScheduling. Spring Batch 같은 전용 도구도.

⚠️ 한 인스턴스로 시작하다 분산 갈 때

신규 프로젝트 초기 — 한 인스턴스라 @Scheduled 단순 사용 OK. 트래픽 증가로 인스턴스 늘릴 때 ShedLock 추가 가 표준. 초기에 너무 복잡하게 설계하지 말고, 필요 시점에 마이그레이션.

한 줄 정리 — @EnableScheduling + @Scheduled 한 줄로 매분·매시간·매일 자동 실행. cron 표현식(6자리)이 가장 자주. 여러 인스턴스 환경에서는 ShedLock으로 분산 락.

시험 직전 한 번 더 — @Scheduled 입문자가 매번 헷갈리는 것

  • @EnableScheduling = 메인 클래스에 박아 활성화
  • @Scheduled = 메서드에 박아 주기 실행
  • 3가지 방식 = fixedRate·fixedDelay·cron
  • fixedRate = 실행 시작 시점 기준 (시간 정밀)
  • fixedDelay = 종료 시점 기준 (안정)
  • cron = Cron 표현식. 매일·매주·매월 정해진 시각
  • Cron = 6자리 (초·분·시·일·월·요일)
  • 0 0 3 * * * = 매일 새벽 3시
  • 0 */5 * * * * = 5분마다
  • 0 0 9 * * MON-FRI = 평일 09시
  • initialDelay = 시작 후 첫 실행 지연
  • 기본 = 단일 스레드 순차 실행
  • 동시 실행 = @EnableAsync + @Async 조합
  • 분산 환경 함정 = 인스턴스마다 따로 실행
  • 해결 1 = 한 인스턴스만 활성화 (@ConditionalOnProperty)
  • 해결 2 = ShedLock 분산 락 (한국 표준)
  • 해결 3 = 별도 배치 서버 분리
  • 큰 시스템 = Spring Batch 전용 도구
  • 메서드는 반환 X, 매개변수 X (void 메서드만)
  • 예외 발생 시 다음 스케줄에 영향 X (개별 실행)
  • 로깅 + 알림 필수 — 실패 시 사일런트 위험

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!