티스토리 뷰
개념
- 스케줄링이란 시간을 기반으로 작업을 예약하여 자동으로 실행하는 것
- 사람이 매번 수동으로 실행하지 않아도 정해진 스케줄에 따라 작업이 돌아가도록 함으로써 시스템 운영을 자동화할 수 있음
스케줄링의 필요성
정기적 배치 작업
- 매일 자정에 데이터 백업을 뜨거나, 매주 통계 리포트를 생성하는 등 정해진 시간마다 수행해야 하는 배치 작업들이 있음
- 스케줄링을 통해 이러한 작업을 자동으로 처리할 수 있음
주기적 상태 점검 및 유지보수
- 애플리케이션의 상태를 주기적으로 모니터링하거나 로그 정리, 캐시 갱신, 임시 파일 삭제 등의 유지보수 작업을 일정 간격마다 실행할 수 있음
이벤트 알림/예약 업무
- 특정 시간에 이메일이나 문자 알림을 발송하거나, 스케줄된 마케팅 캠페인을 실행하는 등 예약된 이벤트를 구현할 때도 스케줄링이 사용
외부 연동 작업
- 주기적으로 API를 호출하여 데이터를 동기화하거나, 큐/메시지 시스템을 폴링(polling)하는 등 반복적인 연동 작업에도 활용

Spring @Scheduled
- Spring은 간단한 스케줄링 작업을 위해 @Scheduled 애노테이션을 제공
- 이 애노테이션을 사용하면 메서드에 실행 주기나 시점을 지정하여 자동으로 호출되도록 할 수 있음
@EnableScheduling으로 스케줄링 활성화
- 먼저 스케줄링을 사용하기 위해서는 Spring에서 이를 활성화(enable) 해야 함
- Spring Boot 애플리케이션의 메인 클래스나 설정 클래스에 @EnableScheduling 애노테이션을 추가하면 애플리케이션 구동 시 스케줄링 기능이 켜짐
@SpringBootApplication
@EnableScheduling
public class Application {
// ...
}
위와 같이 설정하면 이제부터 @Scheduled가 붙은 메서드들이 스케줄에 따라 실행됨 (@SpringBootApplication이 붙은 메인 클래스에 바로 @EnableScheduling을 추가하는 것이 일반적)
@Scheduled 애노테이션 사용법
- @Scheduled는 Spring 관리 빈(bean)의 메서드에 적용
- 예를 들어, @Component나 @Service 클래스로 등록된 빈에 스케줄링 메서드를 만들어 사용할 수 있음
- 스케줄링 메서드는 반환값이 void이고 매개변수가 없어야 함 (스케줄러가 호출할 때 전달할 인수가 없기 때문)
속성
fixedRate
- 고정 주기로 반복 실행
- 이전 실행이 시작된 시점으로부터 지정한 주기가 지나면 바로 다음 실행을 시작
- 예를 들어 fixedRate = 5000으로 설정하면 이전 실행 시작 시간으로부터 5초마다 메서드를 호출
(주의: 기본 설정에서는 단일 스레드로 실행되므로, 이전 작업이 아직 끝나지 않았으면 다음 작업은 대기하게 됩니다. 만약 여러 스레드로 동시에 실행하고 싶다면 스레드 풀 설정이 필요)
fixedDelay
- 고정 지연 간격으로 반복 실행
- 이전 실행이 끝난 시점으로부터 지정한 시간 후에 다음 실행을 시작
- 예를 들어 fixedDelay = 5000으로 설정하면 직전 작업 종료 시각으로부터 5초 뒤에 다음 작업을 실행
- 작업 수행 시간이 가변적일 때 유용함
initialDelay
- 첫 실행을 하기 전에 대기할 시간(ms)을 지정
- fixedRate나 fixedDelay와 함께 사용하여, 애플리케이션 시작 후 첫 작업을 지연시켜 실행할 수 있음
(예: @Scheduled(initialDelay = 10000, fixedRate = 5000)이면 애플리케이션 시작 10초 후 첫 실행, 이후 5초마다 실행)
cron
- Cron 표현식을 사용하여 복잡한 일정을 지정할 수 있음
- Cron 표현식은 리눅스의 cron 스케줄러 형식과 유사하며 "초 분 시 일 월 요일"의 6개 필드로 구성된 문자열 (Spring에서는 6개의 필드를 사용하고 연도(year)는 옵션으로 취급)
- 예를 들어 "0 0 1 * * *"는 "매일 새벽 1시 정각"을 의미
- "0 0/10 * * * *"는 "매 시각 0분 10분 20분 ... 50분에 실행"처럼 10분 간격으로 동작하게 할 수 있음
Cron 표현식을 사용하면 요일이나 월별 패턴 등도 설정할 수 있어 매우 유연
(Cron 표현식의 각 필드초, 분, 시, 일(월), 월, 요일 순서 *는 모든 값, */5는 5 단위마다, ?는 해당 필드 무시 등 다양한 표기법이 있습니다.)
추가사항
- Spring의 @Scheduled에는 cron 속성 외에 zone 속성을 통해 타임존을 지정할 수도 있음
- 기본적으로 서버 로컬 시간대를 사용하지만, 필요한 경우 zone = "Asia/Seoul"처럼 설정하여 다른 시간대 기준으로 스케줄링할 수 있음
- (Deprecated in latest Spring: cron 표현식에서 6개의 필드 대신 7번째 필드로 연도를 지정할 수도 있었으나, 일반적으로 6개 필드만 사용)
@Component
public class MyTasks {
// 1) 매 분마다 실행 (Cron 표현식 예시: 매 분 0초에 실행)
@Scheduled(cron = "0 * * * * *")
public void taskCronExample() {
System.out.println("매 1분마다 실행: " + LocalTime.now());
}
// 2) 10초 간격 fixedRate 실행
@Scheduled(fixedRate = 10000)
public void taskFixedRateExample() {
System.out.println("10초마다 (fixedRate) 실행: " + LocalTime.now());
}
// 3) 15초 간격 fixedDelay 실행 (첫 실행 5초 지연)
@Scheduled(initialDelay = 5000, fixedDelay = 15000)
public void taskFixedDelayExample() {
System.out.println("15초 간격 (fixedDelay) 실행: " + LocalTime.now());
}
}
- taskCronExample()은 매 1분마다 실행 (cron = "0 * * * * *"는 초=0, 나머지 필드는 * 이므로 매 분 0초마다)
- taskFixedRateExample()은 애플리케이션 시작 후 곧바로 실행되고, 이후 10초마다 주기적으로 실행
- taskFixedDelayExample()은 애플리케이션 시작 5초 후 처음 실행되고, 이전 실행이 완료된 시점으로부터 15초 후에 다음 실행을 함
기본 스케줄러 설정에서는 단일 스레드로 모든 작업을 처리하기 때문에, 동시에 여러 작업을 실행하거나 하나의 작업이 긴 시간을 점유해도 다른 작업에 영향이 없도록 하려면 스레드 풀 설정을 조정해야 함
Spring Boot 2.1 이상에서는 application.properties에 spring.task.scheduling.pool.size 값을 지정하여 스케줄러 쓰레드 풀 크기를 늘릴 수 있음 (기본값은 1 )
예를 들어 spring.task.scheduling.pool.size=5로 설정하면 최대 5개의 작업을 병렬로 처리할 수 있음
Quartz
- Quartz는 자바 애플리케이션에서 사용할 수 있는 오픈 소스 스케줄러 프레임워크로, 매우 강력하고 유연한 스케줄링 기능을 제공
- Spring에서도 Quartz를 통합하여 사용할 수 있는데, Quartz를 활용하면 @Scheduled보다 훨씬 세밀한 제어와 다양한 기능을 얻을 수 있음
기본 개념 (Job / Trigger / JobDetail)
Job (잡)
- 스케줄러가 실행할 작업을 의미
- Job은 보통 특정 작업 로직을 담은 클래스로 구현
- Quartz에서는 org.quartz.Job 인터페이스를 구현하여 Job 클래스를 정의하며, 작업 시에 호출될 execute(JobExecutionContext context) 메서드를 포함합니다.
JobDetail (잡 디테일)
- JobDetail은 Quartz가 Job을 스케줄러에 등록할 때 사용하는 메타정보
- 어떤 Job 클래스를 실행할지, Job의 이름이나 그룹은 무엇인지, 그리고 Job 실행에 필요한 데이터는 무엇인지를 포함
- 쉽게 말해, 어떤 작업(Job)을 어떤 이름으로 등록하고 관리할지를 정의하는 객체
- JobDetail에는 Job 클래스와 함께 JobDataMap이라는 데이터를 담아 둘 수 있는데, 이는 실행 시 해당 Job에 필요한 파라미터를 전달하는 용도로 사용
Trigger (트리거)
- Trigger는 말 그대로 방아쇠로, 언제 Job을 실행할지에 대한 스케줄 정보
- Cron 표현식이나 간격, 특정 시간 등 실행 시점을 정의하며, Quartz 스케줄러는 Trigger에 맞춰 해당 Job을 실행
- 예를 들어, Cron 표현식을 사용하는 CronTrigger, 일정 간격으로 N번 실행하거나 특정 종료시점을 가지는 SimpleTrigger 등이 있음
- 하나의 Job에 대해 여러 Trigger를 설정하여 여러 일정으로 같은 Job을 실행할 수도 있음
Scheduler (스케줄러)
- Quartz 스케줄러 자체를 가리키는 객체로, 등록된 Job들과 Trigger를 관리하고 정해진 시간에 Job을 실행하는 역할
- 애플리케이션 내에서 Scheduler 인스턴스를 통해 Job/Trigger를 추가하거나 제거, 시작/중지 등의 제어를 할 수 있음
- Spring Boot 통합에서는 이 Scheduler를 자동으로 관리해주므로 직접 다룰 일이 많지는 않음
"Scheduler에 Job (작업)과 Trigger (언제)를 등록하면, Scheduler가 Trigger 일정에 따라 Job을 실행"
예를 들어 "매일 9시에 데이터 정리 작업(Job)을 실행(Trigger)"처럼 등록하는 것
Spring Boot에서 Quartz 사용하기
- Spring Boot에서는 Quartz를 손쉽게 통합할 수 있도록 지원
- spring-boot-starter-quartz 의존성을 추가하면, Quartz 관련 빈(bean)과 설정을 자동 구성해주며, 우리가 정의한 Job과 Trigger를 이용해 애플리케이션 구동 시 자동으로 스케줄링을 시작할 수 있음
설정 주요 사항
의존성 추가
- Maven이나 Gradle에 spring-boot-starter-quartz를 추가함으로 Quartz 라이브러리와 Spring 통합이 적용됨
Job 클래스 정의
- Quartz Job을 하나 만들려면 org.quartz.Job 인터페이스를 구현한 클래스를 작성해야 함
- 이 클래스의 execute(JobExecutionContext context) 메서드에 수행할 작업 내용을 코딩
- 이때 해당 클래스에 @Component를 붙여서 Spring이 관리하도록 할 수도 있음
- Spring 관리 빈으로 등록하면, 그 Job 내에서 다른 빈을 주입받을 수도 있음
JobDetail 및 Trigger 빈 등록
- Spring Boot에서 Quartz를 사용할 때는 JobDetail과 Trigger를 스프링 빈으로 등록해두면, 자동으로 Scheduler에 추가
- JobDetail은 JobBuilder를 사용해 생성하고, Trigger는 TriggerBuilder를 사용해 생성
- 보통 별도의 @Configuration 클래스에서 @Bean으로 생성하는 패턴을 사용
스케줄러 설정
- 별도로 Scheduler를 코드에서 만들지 않아도, Spring Boot가 자동으로 SchedulerFactoryBean 등을 통해 Scheduler를 초기화하고 실행
- 필요에 따라 Quartz 특유의 설정(스레드 풀 크기, Job 저장방식 등)을 application.properties에 설정할 수 있음(예: 메모리 저장 vs DB 저장, 미실행 트리거 처리 방식 등)
Quartz는 기본적으로 인메모리(HRAM)에서 스케줄 정보를 관리하지만, 데이터베이스를 이용하여 영속적으로 관리하도록 설정할 수 있음
DB를 사용하면 애플리케이션 재시작 후에도 이전에 등록한 Job/Trigger 정보가 유지되고, 클러스터 모드로 여러 인스턴스 중 하나에서만 Job을 실행하도록 구성할 수도 있음 (Quartz가 DB를 통해 작업 락을 걸어 중복 실행을 방지)
이러한 고급 설정은 spring.quartz.job-store-type=jdbc 등의 속성과 Quartz 전용 테이블 설정으로 가능
// 1. Quartz Job 클래스 구현
@Component
public class SampleJob implements org.quartz.Job {
@Override
public void execute(JobExecutionContext context) {
System.out.println("Quartz Job 실행 중: " + new Date());
// 실행할 비즈니스 로직 구현
}
}
// 2. Quartz 설정 - JobDetail과 Trigger를 빈으로 등록
@Configuration
public class QuartzConfig {
// JobDetail 빈: 어떤 Job을 스케줄링 할지 정의
@Bean
public JobDetail sampleJobDetail() {
// SampleJob 클래스를 Quartz JobDetail로 등록
return JobBuilder.newJob(SampleJob.class)
.withIdentity("sampleJob") // Job의 식별자
.storeDurably() // Durable: 트리거가 없어도 유지되도록 (수동 트리거 가능)
.build();
}
// Trigger 빈: 언제 실행할지 정의 (여기서는 Cron 표현식 사용 예)
@Bean
public Trigger sampleJobTrigger(JobDetail sampleJobDetail) {
return TriggerBuilder.newTrigger()
.forJob(sampleJobDetail)
.withIdentity("sampleTrigger")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/5 * * * ?")) // 매 5분마다 실행
.build();
}
}
- 위 코드에서 SampleJob은 실행할 작업을 정의한 클래스이고, QuartzConfig에서는 해당 Job을 Quartz에 등록하는 JobDetail과 5분마다 실행하는 CronTrigger를 설정하고 있음
- Spring Boot는 애플리케이션 구동 시 이 JobDetail과 Trigger를 감지하여 Quartz Scheduler에 등록하고, 즉시 스케줄링을 시작함
- 결과적으로 애플리케이션이 실행되면 5분마다 SampleJob의 execute() 메서드가 호출
만약 코드로 직접 제어하고 싶다면, Scheduler 빈을 주입받아 scheduler.scheduleJob(jobDetail, trigger)를 호출하여 동적으로 Job을 등록/삭제할 수도 있음
하지만 일반적인 반복 작업은 위와 같이 설정으로 등록해두는 것으로 충분
Quartz는 또한 JobListener/TriggerListener를 통해 작업 시작/완료에 대한 훅을 걸거나, misfire(지정된 시간에 실행하지 못한 경우 처리)에 대한 설정, 일회성(One-time) 작업 scheduling 등 다양한 고급 기능을 제공함
이러한 기능은 필요할 때 Quartz 설정을 추가로 해주어야 하지만, Spring과 통합된 상태에서도 충분히 활용할 수 있음
@Scheduled와 Quartz의 장단점 비교
구현 편의성
- @Scheduled는 필요한 메서드에 애노테이션만 붙이면 되므로 매우 간단하고 빠르게 구현할 수 있음
- 추가 라이브러리도 필요 없고, Spring Boot 기본 기능이라 설정이 최소화됨
- Quartz는 별도의 Job 클래스, JobDetail/Trigger 설정 등이 필요하여 초기 설정이 다소 복잡하고 학습 부담이 있음 (Quartz의 풍부한 기능을 익혀야 하므로 초기 구현 난이도는 @Scheduled보다 높음)
스케줄링 기능의 표현력
- @Scheduled는 간단한 Cron 표현식 또는 고정된 간격의 반복 작업에 적합
- 복잡한 일정(예: 업무일에만 실행, N회 반복 후 중지 등)을 표현하기 어렵거나 코드로 직접 제어해야 함
- Quartz는 Cron 표현식 뿐만 아니라 여러 종류의 Trigger를 통해 매우 유연한 스케줄을 설정할 수 있음
- 특정 시간에 한 번만 실행하는 작업, 작업 간 의존성 설정, 캘린더(휴일 제외 등) 기반 스케줄 등 @Scheduled로는 힘든 시나리오를 지원
영속성 및 내구성
- @Scheduled는 애플리케이션 메모리 내에서 일정이 관리되며, 애플리케이션이 재시작되면 스케줄 정보가 유지되지 않음
- 실행 중 애플리케이션이 중단되면 그 동안 못한 작업을 보존하거나 재시도할 방법이 없음
- Quartz는 JDBC JobStore를 설정하면 스케줄 정보를 DB에 저장하여 애플리케이션 재시작 후에도 작업을 이어갈 수 있음
- 심지어 지정된 시각에 실행하지 못한 작업을 "misfire"로 처리하고, 정책에 따라 다시 실행하거나 건너뛰는 등의 관리가 가능합니다
- 즉, Quartz는 장기적으로 안정적인 스케줄 관리가 필요한 경우에 유리
클러스터링(분산 환경)
- @Scheduled는 각 애플리케이션 인스턴스 내에서 동작하므로, 동일한 애플리케이션이 여러 대 실행 중이라면 모든 인스턴스에서 작업이 중복 실행될 수 있음
- 이는 멀티 노드 환경에서 @Scheduled의 큰 단점
- Quartz는 클러스터 모드를 지원하여, 여러 노드가 같은 스케줄러 DB를 공유하면서 동일 Job의 중복 실행을 방지할 수 있음 (한 노드에서 트리거를 잡으면 다른 노드들은 잡지 않는 식으로 동작)
- 따라서 고가용성이나 분산 락이 필요한 스케줄링에서는 Quartz가 적합
관리 및 모니터링
- @Scheduled로 등록된 작업은 기본적으로 애플리케이션 코드 내에 하드코딩되어 있으며, runtime에 쉽게 변경하거나 관리 UI를 통해 조작하기 어려움
- Quartz는 API를 통해 작업 추가/중지/재시작 등이 가능하고, Quartz 전용 모니터링 툴이나 대시보드를 붙여 실행 이력이나 상태를 모니터링할 수 있음
- 작업별로 식별자가 있으므로 특정 Job만 중지하거나 수정하는 제어가 비교적 용이
성능 및 리소스
- @Scheduled는 경량 스케줄링으로, 스프링의 단순한 스레드 풀만 활용하므로 오버헤드가 작음
- 따라서 단순한 몇 개의 작업만 돌리는 경우 @Scheduled면 충분
- Quartz는 강력한 만큼 추가 리소스가 필요
- 데이터베이스 연동을 하면 DB 연결 및 테이블이 필요하고, 내부에 다양한 기능을 수행하기 때문에 @Scheduled보다 약간 무거움
작은 규모의 애플리케이션이나 몇 가지 간단한 배치 작업만 필요하다면 Spring @Scheduled가 간편하고 충분한 선택
그러나 여러 스케줄을 체계적으로 관리해야 하거나, 애플리케이션 재시작/클러스터링에도 끄떡없이 작업을 보장해야 하는 기업 환경이라면 Quartz의 도입을 고려해야 함
상황에 따라 두 가지를 혼용하기도 하는데, 예를 들어 자주 실행되는 간단한 작업은 @Scheduled로 하고, 중요한 작업은 Quartz로 구성하는 식
실제 코드 예제
Spring @Scheduled 예제 코드
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@SpringBootApplication
@EnableScheduling // 스케줄링 기능 활성화
public class ScheduledDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduledDemoApplication.class, args);
}
}
// 스케줄링할 작업을 정의하는 빈(component)
@Component
class ScheduledTasks {
// 매일 오전 9시 30분에 실행 (cron 사용 예시)
@Scheduled(cron = "0 30 9 * * *")
public void runDailyJob() {
System.out.println("[@Scheduled] 매일 9시30분 실행 - Daily Job 수행");
// 여기에 수행할 작업 로직 (예: 보고서 생성)
}
// 1분마다 실행 (fixedRate 사용 예시)
@Scheduled(fixedRate = 60000)
public void runPeriodicJob() {
System.out.println("[@Scheduled] 1분마다 실행 - Periodic Job 수행");
// 여기에 수행할 작업 로직 (예: 캐시 갱신)
}
}
위 예제는 Spring Boot에서 @Scheduled를 사용한 전형적인 코드
@EnableScheduling을 통해 스케줄링을 활성화했고, ScheduledTasks 빈 내부에 두 개의 메서드를 스케줄링함
- runDailyJob() 메서드는 크론 표현식 "0 30 9 * * *"에 따라 매일 오전 9시 30분에 실행 (초=0, 분=30, 시=9 로 설정하여 매일 9:30:00 AM 트리거)
- runPeriodicJob() 메서드는 fixedRate = 60000ms (60초) 설정에 따라 1분마다 실행됨
- 이전 실행이 시작된 시점 기준 매 60초 간격으로 새 실행이 시작됨
Quartz 예제 코드
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
// 1. Quartz Job 구현
public class MyQuartzJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("[Quartz] 작업 실행 - " + new Date());
// 실제 수행할 작업 로직 (예: 이메일 발송)
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.quartz.JobBuilder;
import org.quartz.TriggerBuilder;
import org.quartz.CronScheduleBuilder;
// 2. Quartz 설정 (JobDetail 및 Trigger 등록)
@Configuration
public class QuartzDemoConfig {
// Quartz JobDetail 빈: MyQuartzJob을 스케줄러에 등록
@Bean
public JobDetail myJobDetail() {
return JobBuilder.newJob(MyQuartzJob.class)
.withIdentity("myQuartzJob") // Job 이름 (식별자)
.storeDurably() // Durable: 트리거 없어도 존재
.build();
}
// Quartz Trigger 빈: 30초마다 myQuartzJob 실행하는 Trigger
@Bean
public Trigger myJobTrigger() {
return TriggerBuilder.newTrigger()
.forJob(myJobDetail()) // 어떤 JobDetail과 연결할지
.withIdentity("myQuartzTrigger") // Trigger 이름
.withSchedule(CronScheduleBuilder.cronSchedule("0/30 * * * * ?"))
// ↑ cron 표현식: 매 30초 간격으로 실행 (0초부터 매 30초마다)
.build();
}
}
이 예제는 Quartz를 활용한 스케줄링 코드
MyQuartzJob 클래스는 Quartz Job을 구현하여, execute() 안에 작업 내용을 정의
QuartzDemoConfig 설정 클래스에서는 myJobDetail() 빈을 통해 해당 Job을 Quartz에 등록하고, myJobTrigger() 빈을 통해 매 30초마다 실행되는 Cron 스케줄을 설정
- Spring Boot에 spring-boot-starter-quartz가 추가되어 있다면, 애플리케이션 실행 시 자동으로 이 JobDetail과 Trigger가 Quartz Scheduler에 등록되고, Scheduler가 시작
- 결과적으로 MyQuartzJob.execute() 메서드가 30초 간격으로 호출되어, 콘솔에 "[Quartz] 작업 실행 ..." 로그가 출력
만약 Quartz를 JDBC 저장소와 함께 사용하도록 설정했다면 (application.properties에서 spring.quartz.job-store-type=jdbc 등을 지정), 처음 애플리케이션 실행 시 Quartz 전용 테이블이 생성되고, Job/Trigger 정보가 그 테이블에 저장됨
이 경우 애플리케이션을 재시작하더라도 이전에 등록한 스케줄이 유지되며 이어서 동작 (예: 앱을 내렸다 올려도 30초 스케줄이 계속 살아있음)
Tip: Quartz를 사용하는 경우, 운영 중에 스케줄을 변경하거나 추가/삭제하려면 Scheduler 객체를 주입받아 scheduleJob, rescheduleJob, pauseJob 등의 메서드를 사용할 수 있음 또는 DB를 직접 조작하거나 별도의 관리 UI를 붙여서 제어하기도 함
반면 @Scheduled 방식은 코드를 수정하고 재배포하지 않으면 스케줄 변경이 어렵다는 점에서 차이가 있음
면접 대비 질문
기초 질문
Q1. 스케줄링(Scheduling)이란 무엇이며, 애플리케이션에서 왜 필요한가요?
모범답안
스케줄링이란 특정 코드나 작업을 미리 정해둔 시간이나 주기마다 자동으로 실행되도록 예약하는 것을 말합니다. 사람이 수동으로 트리거하지 않아도 시스템이 알아서 일을 진행하기 때문에, 반복적인 작업이나 정기적인 작업을 처리하는 데 유용합니다. 예를 들어, 매일 밤 12시에 데이터 백업을 뜨거나, 10분마다 서버의 상태를 체크해서 이상이 있으면 알림을 보내는 작업 등을 스케줄링해둘 수 있습니다. 이렇게 하면 정해진 업무가 누락 없이 실행되고, 운영 효율성이 크게 향상됩니다. 요약하면, 스케줄링은 애플리케이션이 시간 기반으로 작업을 자동 수행할 수 있게 해주는 메커니즘이며, 주기적인 배치 작업이나 유지보수 작업의 자동화 때문에 거의 모든 서버 애플리케이션에서 중요한 역할을 합니다.
Q2. 스케줄링 작업에서 Cron 표현식의 기본 구조와 주요 구성 요소에 대해 설명해주세요.
모범답안
Cron 표현식은 6개 필드(초, 분, 시, 일(월), 월, 요일)로 구성되며, 각 필드는 작업이 실행될 조건을 지정합니다. 예를 들어 "0 0 9 * * *"는 매일 오전 9시 정각에 실행함을 의미합니다. 각 필드에서 *는 모든 값을 의미하고, /는 단위 간격, ,는 여러 값을 지정할 수 있습니다. 이처럼 Cron 표현식은 정교하게 반복 실행 조건을 설정할 수 있는 강력한 도구입니다.
심화 질문
Q1. Spring의 @Scheduled와 Quartz 스케줄러의 차이점은 무엇인가요?
모범답안
두 방식 모두 작업을 일정에 따라 실행한다는 목적은 같지만 기능적 범위와 구현 방식에서 큰 차이가 있습니다.
우선 Spring @Scheduled는 Spring 프레임워크 내장 기능으로, 간단한 애노테이션 설정만으로 사용 가능합니다. @Scheduled는 주로 Cron 표현식이나 고정된 시간 간격을 설정하여 간단한 주기적 작업에 적합합니다. 별도의 인프라 설정이 필요 없고 가벼운 반면, 작업 스케줄이 애플리케이션 메모리에만 존재하기 때문에 애플리케이션 재시작 시 스케줄 정보가 사라지고, 여러 대 서버에서 동작할 경우 인스턴스 수만큼 작업이 중복 실행되는 단점이 있습니다. 또한 동적 제어나 복잡한 스케줄(예: 영업일에만 실행, N회 반복 후 종료 등)을 표현하기 어렵습니다.
반면 Quartz는 외부 라이브러리 기반의 풀-기능 스케줄러입니다. Job/Trigger 개념을 통해 하나의 작업에 여러 스케줄을 붙일 수도 있고, 작업 상태를 DB에 저장하여 애플리케이션이 내려갔다 올라와도 스케줄을 이어서 진행할 수 있습니다. Quartz는 클러스터 모드를 지원해서 여러 인스턴스 중 하나만 작업을 수행하도록 조정할 수 있으므로, 분산 환경에서 작업 중복 실행을 방지할 수 있습니다. 또한 일회성 작업, 특정 기간에만 유효한 스케줄, 작업 간 의존성, 미실행된 작업의 처리(Misfire 정책) 등 더 풍부한 기능을 제공합니다. 다만 이러한 유연성 때문에 설정이 복잡하고, DB 등 부가 리소스 설정이 필요해 구현 난이도와 유지 보수 부담이 높은 편입니다.
정리하면, 간단하고 단순한 스케줄링 작업에는 개발과 관리가 쉬운 @Scheduled가 적합하고, 복잡하고 중요한 스케줄 관리에는 Quartz 같은 전문 스케줄러를 사용하는 것이 좋습니다.
Q2. 스케줄링 작업이 클러스터 환경에서 중복 실행되는 문제를 어떻게 해결할 수 있나요?
모범답안
클러스터 환경에서는 여러 인스턴스가 동시에 같은 작업을 실행하는 문제(중복 실행)가 발생할 수 있습니다. Spring @Scheduled는 기본적으로 로컬에서만 실행되기 때문에 이 문제가 발생할 수 있습니다. Quartz를 사용하는 경우에는 클러스터 모드를 활성화하여 DB 기반 JobStore를 사용하고, Quartz의 락 메커니즘을 통해 한 노드에서만 Job을 실행하도록 제어할 수 있습니다. 또한, 별도의 분산 락 솔루션(예: Redis 기반 락)을 도입해 여러 인스턴스 간에 락을 공유하는 방식으로 중복 실행을 방지할 수도 있습니다.
압박 질문
Q1. 스케줄링된 작업(@Scheduled 또는 Quartz Job)이 실행되는 도중에 예외가 발생하면 어떤 일이 벌어지며, 이를 어떻게 처리하는 것이 좋을까요?
모범답안
스케줄링된 작업이 실행 중 예외(Exception)를 던질 경우, 해당 실행은 실패로 끝나고 스택트레이스가 로그에 남게 됩니다. 그러나 그 이후 동작은 사용하는 스케줄러에 따라 차이가 있습니다. Spring의 @Scheduled의 경우 특정 실행에서 예외가 발생해도 다음 스케줄 시각에는 새로운 스레드로 작업을 재시도합니다. (기본적으로 Spring 스케줄러는 ScheduledExecutorService를 사용하며, 주기적인 작업에서 예외가 발생하면 해당 작업 스레드가 종료되지만 스레드 풀 덕분에 다음 실행 때는 새로운 스레드가 투입됩니다.) 하지만 자바 스케줄러의 특성상 주기 실행(task)이 예외로 종료되면 subsequent execution이 멈추는 경우도 있을 수 있어, 안전하게 하려면 예외를 잡아서(logging) 처리하는 것이 좋습니다. 한편 Quartz의 경우도 Job 실행 중 발생한 예외는 기본적으로 상위로 전파되지 않고 JobExecutionException으로 래핑할 수 있습니다. 이 예외에 특정 설정을 주면 해당 Job 트리거의 다음 동작을 중지하거나 재시도 여부를 결정할 수 있습니다. 예를 들어, JobExecutionException#setRefireImmediately(true)를 호출하면 즉시 재시도하도록 할 수도 있습니다.
실무적으로 중요한 것은, 스케줄링된 작업 내부에서는 발생할 수 있는 예외를 자체적으로 핸들링하여, 예외로 인해 스케줄러 전체가 멈추거나 해당 작업이 영구히 중단되지 않도록 하는 것입니다. 예를 들어, @Scheduled 메서드 안에서 try-catch로 예외를 잡고 로그만 남긴 뒤 정상 흐름을 유지한다면 다음 주기에 계속 실행될 것입니다. Quartz Job의 경우도 마찬가지로, 예외 상황을 예상하여 적절히 catch하거나, 필요하면 Quartz의 JobListener 등을 활용해 에러를 감시하고 대처해야 합니다. 또한 이메일 전송 실패나 외부 시스템 연계 실패처럼 예상 가능한 예외 상황에 대한 재시도 로직을 넣는 등 견고하게 작업을 설계하는 것이 바람직합니다. 결국 면접에서는 스케줄러에서 예외가 발생해도 시스템이 안정적으로 돌아가게 하는 방법(예외처리, 재시도, 알림)에 대해 답변하면 좋은 평가를 받을 것입니다.
Q2. 스케줄링 작업에서 'Misfire' 상황이란 무엇이며, 이를 어떻게 처리하는 것이 좋을까요?
모범답안
'Misfire'란 스케줄링된 작업이 실행되어야 하는 시점에 어떤 이유(예: 서버 다운, 과부하 등)로 인해 작업이 실행되지 못한 상황을 의미합니다. Quartz에서는 Misfire 발생 시 기본 처리 정책을 제공하며, 필요에 따라 misfireInstruction을 설정해 다음과 같이 대응할 수 있습니다.
- 즉시 재시도: 가능한 빠르게 누락된 작업을 다시 실행
- 스킵: 단순히 다음 예정 작업으로 넘어감
- 누적 실행: 미실행 작업들을 한꺼번에 처리하도록 하는 방법 등
면접에서는 Misfire 상황에 대비해 비즈니스 로직과 작업의 중요도에 따라 적절한 정책을 선택하고, 필요하다면 로그와 알림 시스템을 통해 운영자가 문제를 인지할 수 있도록 설계하는 것이 중요하다는 점을 언급하면 좋습니다.
출처
'Spring' 카테고리의 다른 글
| [Spring] Spring Boot (0) | 2025.03.11 |
|---|---|
| [Spring] Spring Cloud와 Microservices 기초 (1) | 2025.03.10 |
| [Spring] AOP(Aspect Oriented Programming) (0) | 2025.02.26 |
| [Spring] H2 Database & Spring Boot DevTools (0) | 2025.02.25 |
| [Spring] 트랜잭션 관리(@Transactional) (0) | 2025.02.25 |
- Total
- Today
- Yesterday
- 해시 테이블
- devops
- 분할 정복
- Java
- 그리디 알고리즘
- 우선순위 큐
- 탐색 알고리즘
- 프리코스
- 알고리즘
- 자바
- Spring
- db
- 동적 프로그래밍
- restful api
- 백트래킹
- 우아한 테크코스
- HTTP
- CPU 스케줄링
- 운영체제
- 데이터베이스
- 우테코
- 자료구조
- 스프링
- k8
- B+Tree
- MSA
- CS
- TRIE
- i/o모델
- Spring Boot
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |