← 스터디 홈
2편 · 약 9분

스케줄링과 backfill

Airflow 스케줄링의 핵심 규칙

Airflow 스케줄링에서 가장 많이 혼란을 야기하는 규칙이 하나 있다.

DAG 실행은 해당 데이터 구간(data interval)이 끝난 뒤에 시작된다.

@daily DAG의 start_date2025-01-01이면, 2025-01-01 00:00 ~ 2025-01-02 00:00 구간의 첫 실행은 2025-01-02 00:00 이후에 시작된다. 실행 버튼을 누른 시각이 아니라 처리 대상 데이터 구간을 기준으로 스케줄하기 때문이다.

이 원칙을 이해하면 나머지 개념들이 자연스럽게 풀린다.

schedule 파라미터

schedule에는 세 가지 형태를 쓸 수 있다.

# 1. cron 문자열
schedule="0 1 * * *"        # 매일 새벽 1시

# 2. cron 프리셋
schedule="@daily"           # 매일 자정
schedule="@hourly"          # 매시 정각
schedule="@weekly"          # 매주 일요일 자정

# 3. timedelta
from datetime import timedelta
schedule=timedelta(hours=6)  # 6시간마다
프리셋동등한 cron
@once한 번만 실행
@hourly0 * * * *
@daily0 0 * * *
@weekly0 0 * * 0
@monthly0 0 1 * *
@yearly0 0 1 1 *

data_interval과 logical_date

Airflow 2.2 이전에는 execution_date라는 개념이 있었다. 이름 때문에 "실행 시각"으로 오해하기 쉬웠고, 실제로는 데이터 구간의 시작점을 뜻했다. Airflow 2.2부터 더 명확한 이름으로 바뀌었다.

  • data_interval_start: 이 DAG 실행이 담당하는 데이터 구간의 시작
  • data_interval_end: 데이터 구간의 끝
  • logical_date: data_interval_start와 동일 (execution_date의 새 이름)
1월 1일 자정 data_interval_start
2025-01-01
1월 2일 자정 data_interval_end
2025-01-02
실제 실행 시작 DAG Run 시작
2025-01-02 00:00+
1월 2일 자정 data_interval_start
2025-01-02
1월 3일 자정 data_interval_end
2025-01-03
실제 실행 시작 DAG Run 시작
2025-01-03 00:00+
@daily DAG의 스케줄 타임라인 — 실행 시점은 구간이 끝난 후다

Task 코드에서 이 값을 참조하려면 Jinja 템플릿 변수를 쓴다.

BashOperator(
    task_id="load",
    bash_command="python load.py --date {{ data_interval_start | ds }}",
)

{{ data_interval_start | ds }}2025-01-01 형식의 날짜 문자열로 렌더링된다.

catchup — 과거 구간 자동 보완

DAG를 새로 만들거나, 오랫동안 일시정지했다가 다시 켰을 때 start_date와 현재 시각 사이에 실행되지 않은 구간이 쌓여 있을 수 있다. catchup 파라미터가 이 동작을 제어한다.

with DAG(
    dag_id="daily_report",
    schedule="@daily",
    start_date=datetime(2025, 1, 1),
    catchup=True,    # 기본값: 미실행 구간 모두 채운다
) as dag:
    ...
  • catchup=True (기본): 스케줄러가 미실행 구간을 모두 DAG Run으로 생성해 순차적으로 실행한다.
  • catchup=False: 가장 최근 구간 하나만 실행한다. 과거 이력은 무시.

운영 환경에서 catchup=True를 기본으로 두면 DAG를 재배포할 때 예상치 못한 폭발적 실행이 생길 수 있다. 대부분의 팀은 catchup=False를 기본으로 설정하고, 필요할 때만 backfill을 수동으로 실행한다.

backfill — 수동 과거 구간 재실행

Catchup이 스케줄러의 자동 동작이라면, backfill은 사용자가 명시적으로 특정 날짜 범위를 재실행하도록 요청하는 작업이다.

언제 쓰나

  • 파이프라인 버그를 수정한 뒤 영향받은 기간을 재처리할 때
  • 새 DAG를 배포하면서 과거 데이터를 소급 처리할 때
  • 다운타임 복구 후 누락 구간을 채울 때

CLI backfill

Airflow 2.x에서는 CLI로 backfill을 실행했다.

# 2025년 1월 한 달치 재실행
airflow dags backfill \
  --start-date 2025-01-01 \
  --end-date 2025-01-31 \
  daily_report

# 실제 실행 전 어떤 구간이 생성되는지 확인 (dry-run)
airflow dags backfill \
  --start-date 2025-01-01 \
  --end-date 2025-01-31 \
  --dry-run \
  daily_report

Airflow 3.0 변화: Scheduler-Managed Backfill

Airflow 3.0부터 backfill은 별도 CLI 프로세스가 아니라 Scheduler가 직접 관리하는 방식으로 바뀌었다. 이로 인해 backfill 실행도 일반 DAG Run과 동일한 스케줄링·버전 관리·모니터링 흐름을 따른다.

# Airflow 3.x: backfill 생성 (Scheduler가 처리)
airflow dags backfill create --dag-id daily_report \
  --from-date 2025-01-01 --to-date 2025-01-31

catchup vs backfill 비교

catchup=True Scheduler 자동 동작 start_date 이후
미실행 구간 전체
자동 생성
vs
backfill 사용자 수동 요청 지정 날짜 범위만
선택적 재실행
(버그 수정·소급처리)
catchup과 backfill 동작 비교
항목catchupbackfill
트리거자동 (Scheduler)수동 (CLI / UI)
범위start_date ~ 현재 미실행 전체지정 날짜 범위
주 용도재시작 후 연속성 보장버그 수정 후 재처리
제어catchup=True/False--start-date / --end-date

max_active_runs — 동시 실행 제한

catchup이 활성화되면 수십 개의 DAG Run이 동시에 실행될 수 있다. max_active_runs로 동시 실행 수를 제한해 리소스 과부하를 방지한다.

with DAG(
    dag_id="daily_report",
    schedule="@daily",
    start_date=datetime(2025, 1, 1),
    catchup=True,
    max_active_runs=3,   # 동시에 최대 3개 DAG Run만
) as dag:
    ...

References

  • https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/dag-run.html
  • https://airflow.apache.org/blog/airflow-three-point-oh-is-here/
  • https://airflow.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/timetable.html
  • https://medium.com/@su-paris/apache-airflow-4-scheduling-and-catchup-3d733f92a2bc
  • https://faun.pub/airflow-catchup-vs-backfill-22f3a7d4ba6f