
데이터 파이프라인을 운영하다 보면 "매일 새벽에 ETL을 돌리고 싶다", "평일 오후에만 리포트를 생성하고 싶다", "3일마다 한 번씩 배치를 돌리고 싶다" 같은 요구가 생깁니다. Apache Airflow에서는 DAG의 schedule 파라미터로 이런 실행 주기를 정의합니다. 스케줄 방식을 잘못 설정하면 과거 구간이 한꺼번에 돌아가는 catchup 문제가 생기거나, 의도와 다른 시간에 DAG가 실행될 수 있어요. 이 글에서는 Airflow가 제공하는 4가지 스케줄링 방식(Presets, Cron, Timedelta/Delta, Timetable)을 개념부터 코드 예시, 실무 선택 기준까지 정리합니다.
Airflow에서 DAG 실행 시점을 정하는 방식은 크게 네 가지로 나눌 수 있습니다.
@daily, @hourly 등 고정된 의미의 문자열주요 기능:
실무 관점:
@daily 한 줄로 해결0 9 * * *)으로 전환하는 경우가 많음언제 필요한가?
| Preset | 의미 |
|---|---|
@daily | 매일 자정 (0 0 * * *) |
@hourly | 매 시간 정각 (0 * * * *) |
@weekly | 매주 일요일 자정 (0 0 * * 0) |
@monthly | 매월 1일 자정 (0 0 1 * *) |
@yearly | 매년 1월 1일 자정 |
@once | 한 번만 실행 |
None | 스케줄 없음 (수동 트리거) |
from airflow import DAG
from airflow.operators.empty import EmptyOperator
from pendulum import datetime
with DAG(
dag_id="preset_example",
schedule="@daily", # 매일 자정 실행
start_date=datetime(2026, 1, 1, tz="Asia/Seoul"),
catchup=False,
):
task = EmptyOperator(task_id="daily_task")
주요 기능:
실무 관점:
0 16 * * MON-FRI → 평일 오후 4시, 0 0 1,15 * * → 매월 1일·15일 자정start_date 등에서 사용하는 datetime에 tz를 지정해 일관되게 맞추는 것이 중요언제 필요한가?
┌─────── 분 (0-59)
│ ┌───── 시 (0-23)
│ │ ┌─── 일 (1-31)
│ │ │ ┌─ 월 (1-12)
│ │ │ │ ┌ 요일 (0-6, 0=일요일)
│ │ │ │ │
* * * * *
from airflow import DAG
from airflow.operators.empty import EmptyOperator
from pendulum import datetime
with DAG(
dag_id="cron_example",
schedule="0 16 * * MON-FRI", # 평일 오후 4시
start_date=datetime(2026, 1, 1, tz="Asia/Seoul"),
catchup=False,
):
task = EmptyOperator(task_id="weekday_afternoon_task")
자주 쓰는 패턴:
0 9 * * * → 매일 오전 9시*/30 * * * * → 30분마다0 0 1,15 * * → 매월 1일, 15일 자정주요 기능:
schedule=timedelta(days=3)처럼 넣으면 Airflow가 내부적으로 DeltaDataIntervalTimetable로 변환실무 관점:
timedelta(days=3), timedelta(hours=6), timedelta(minutes=30)언제 필요한가?
from airflow import DAG
from airflow.operators.empty import EmptyOperator
from pendulum import datetime
from datetime import timedelta
with DAG(
dag_id="timedelta_simple_example",
schedule=timedelta(days=3), # 3일마다 실행
start_date=datetime(2026, 1, 1, tz="Asia/Seoul"),
catchup=False,
):
task = EmptyOperator(task_id="every_3_days_task")
주요 기능:
실무 관점:
schedule=timedelta(...)와 동작은 동일함. 다만 "Timetable을 쓰고 있다"는 것이 코드에 드러남언제 필요한가?
from airflow import DAG
from airflow.operators.empty import EmptyOperator
from airflow.timetables.simple import DeltaDataIntervalTimetable
from pendulum import datetime
from datetime import timedelta
every_3_days = DeltaDataIntervalTimetable(timedelta(days=3))
with DAG(
dag_id="delta_timetable_example",
schedule=every_3_days,
start_date=datetime(2026, 1, 1, tz="Asia/Seoul"),
catchup=False,
):
task = EmptyOperator(task_id="every_3_days_task")
| 구분 | timedelta 직접 사용 | DeltaDataIntervalTimetable |
|---|---|---|
| 코드 | 간결함 | 명시적 |
| 내부 동작 | 자동으로 Timetable로 변환됨 | 직접 Timetable 사용 |
| 사용 시점 | 단순한 경우 | 커스텀 Timetable과 조합 시 |
💡
schedule=timedelta(...)를 넣으면 Airflow 내부에서DeltaDataIntervalTimetable로 자동 변환됩니다. 결과는 동일합니다.
주요 기능:
실무 관점:
event_dates 리스트를 코드나 설정에서 생성해 넘기면 됨catchup=True와 조합하면 지정한 과거 날짜들도 한 번씩 실행 가능언제 필요한가?
from airflow.sdk import dag, task
from pendulum import datetime
from airflow.timetables.events import EventsTimetable
special_dates = EventsTimetable(
event_dates=[
datetime(2026, 1, 1, tz="Asia/Seoul"),
datetime(2026, 1, 15, tz="Asia/Seoul"),
datetime(2026, 1, 26, tz="Asia/Seoul"),
datetime(2026, 1, 30, tz="Asia/Seoul"),
]
)
@dag(
schedule=special_dates,
start_date=datetime(2026, 1, 1, tz="Asia/Seoul"),
end_date=datetime(2026, 1, 31, tz="Asia/Seoul"),
catchup=True,
)
def events_timetable_example():
@task
def run_on_special_date():
print("특별한 날에만 실행!")
run_on_special_date()
events_timetable_example()
| 방식 | 사용 시점 | 예시 |
|---|---|---|
| Presets | 단순하고 일반적인 주기 | @daily, @hourly |
| Cron | 특정 시간/요일/일자 지정 필요 | 0 16 * * MON-FRI |
| Timedelta | 고정 간격 실행 | timedelta(days=3) |
| DeltaDataIntervalTimetable | 고정 간격 (명시적) | Timetable 객체로 조합 |
| EventsTimetable | 불규칙 날짜, 이벤트 기반 | 공휴일, 이벤트 날짜 등 |
# Delta 관련
from datetime import timedelta
from airflow.timetables.simple import DeltaDataIntervalTimetable
# Events 관련
from airflow.timetables.events import EventsTimetable
# Cron 관련 (필요시)
from airflow.timetables.simple import CronDataIntervalTimetable
신규 DAG 설계 시 선택 순서
1. "매일/매시간/매주" 수준이면 → Presets (@daily, @hourly 등)
2. "평일 오후 4시", "매월 1일·15일"처럼 구체 시각/요일/일자가 필요하면 → Cron
3. "N시간/ N일마다" 간격이 중요하면 → Timedelta (또는 필요 시 DeltaDataIntervalTimetable)
4. 날짜가 불규칙하면 → EventsTimetable 또는 커스텀 Timetable
주의사항
start_date와 catchup: 과거 구간을 채울지 여부를 catchup로 제어. 기본값이 True이므로 의도치 않은 대량 실행을 막으려면 catchup=False를 자주 사용함pendulum.datetime(..., tz="Asia/Seoul") 등으로 DAG와 태스크에서 타임존을 통일할 것schedule이 내부적으로 어떻게 Timetable로 매핑되는지 이해하면 디버깅과 확장에 유리함catchup과 타임존 설정을 함께 점검하면 예상치 못한 실행을 줄일 수 있습니다.참고 자료: