Catchup, Backfill, 재실행과 태스크 클리어
Apache Airflow Official Documentation (in-repo snapshot) — Apache Software Foundation core-concepts/dag-run.rst - Catchup, Backfill, Re-run Tasks, Task Instance History, External Triggers, Passing Parameters, Wait for a Dag Run
이 모듈을 다 읽으면
- catchup=True/False가 CronTriggerTimetable과 데이터 인터벌 타임테이블에서 각각 어떤 실행을 생성하는지 설명할 수 있다
- Backfill과 Catchup의 차이, 그리고 태스크 클리어 시 try_number/max_tries/state가 어떻게 바뀌는지 설명할 수 있다
- Past/Future/Upstream/Downstream/Recursive/Failed 클리어 옵션의 차이를 설명할 수 있다
이 모듈은 Dag를 과거 시점에 대해 다시 실행하는 세 가지 메커니즘 — catchup, backfill, 태스크 재실행(clear) — 을 다룬다. Airflow 3에서 catchup의 동작이 타임테이블 종류에 따라 어떻게 달라지는지, backfill이 catchup과 어떻게 다른지, 그리고 태스크 인스턴스 히스토리와 파라미터를 넘겨 Dag를 트리거하는 방법도 함께 다룬다.
Catchup: 기본은 꺼져 있고, 타임테이블에 따라 동작이 다르다
``start_date``, 선택적으로 ``end_date``, 그리고 asset이 아닌 스케줄을 가진 Dag는 스케줄러가 개별 Dag Run으로 변환해 실행할 일련의 예약 실행 시각들을 정의한다. 기본적으로(``scheduler.catchup_by_default=False``) start_date와 '지금' 사이의 놓친 예약 실행 시각들은 Dag가 활성화될 때 자동으로 백필되지 않는다 — 대신 타임테이블이 가장 최근에 적용 가능한 예약 실행 시각을 선택한다(트리거 타임테이블이라면 일반적으로 '지금'보다 늦지 않고 start_date보다 이르지 않은 가장 최근 cron 틱).
``catchup=True``로 설정하면 스케줄러는 마지막 실행 이후(혹은 클리어된) 아직 실행되지 않은 모든 예약 실행 시각에 대해 Dag Run을 시작한다. Dag가 catchup을 스스로 처리하도록 작성되지 않았다면(즉 해당 인터벌에 국한되지 않고 대신 '지금'을 기준으로 동작한다면) catchup을 꺼두는 것이 좋다.
Airflow 3에서 CronTriggerTimetable 기본값과 catchup=False 조합에서는, 예를 들어 스케줄러가 2016-01-02 오전 6시에 Dag를 처음 인식했다면 start_date 이후 매 자정마다 실행을 만드는 것이 아니라, 가장 최근에 적용 가능한 틱(2016-01-02 자정) 하나에 대해서만 단일 Dag Run을 만들고 data_interval_start/end 모두 그 트리거 시각과 같게 설정한다. 이 run_after가 이미 과거이므로 즉시 시작할 수 있고, 다음 틱(2016-01-03 자정)은 그 시각이 도래해야 생성된다. 반대로 데이터 인터벌 타임테이블을 쓴다면, 스케줄러는 가장 최근에 완료된 인터벌(2016-01-01~2016-01-02)에 대해 즉시 실행을 만들고, 다음 실행은 그 인터벌이 끝난 뒤 2016-01-02~2016-01-03을 다룬다.
catchup=True였다면 스케줄러는 start_date와 '지금' 사이의 모든 예약 실행 시각에 대해 Dag Run을 만들었을 것이다 — 기본 트리거 타임테이블 기준으로는 2015-12-01부터 2016-01-02까지의 모든 자정이, 데이터 인터벌 타임테이블 기준으로는 다음 자정에 끝나는(아직 열려 있는) 인터벌을 제외한 모든 인터벌이 대상이다. Dag를 특정 기간 동안 껐다가 다시 켜는 것도 catchup을 유발한다.
핵심 포인트
- 기본값은 catchup_by_default=False이며, 이 경우 Airflow 3의 CronTriggerTimetable은 start_date~현재 사이 모든 틱이 아니라 가장 최근 적용 가능한 틱 하나만 실행으로 생성한다
- catchup=True로 설정하면 start_date부터 현재까지의 모든 미실행 예약 시각에 대해 Dag Run이 생성된다
- 데이터 인터벌 타임테이블에서는 아직 끝나지 않은(열려 있는) 인터벌은 catchup=True에서도 생성되지 않는다
Backfill: 특정 과거 기간을 지정해 다시 실행하기
Backfill은 특정 과거 기간에 대해 Dag를 실행하고 싶을 때 쓰는 절차다. 예를 들어 Dag의 start_date가 2024-11-21이지만 한 달 전인 2024-10-21 데이터의 출력이 필요한 경우다. UI에서는 Dag Details 페이지에서 Trigger를 클릭하고 Backfill을 선택하면 날짜 범위, 재처리(reprocess) 방식, 최대 동시 실행 수, 역순 실행 여부, Advanced Config를 설정하는 폼이 열린다. CLI에서는 ``airflow backfill create --dag-id DAG_ID --from-date START_DATE --to-date END_DATE --reprocess-behavior failed --max-active-runs 3 --run-backwards --dag-run-conf '{"my": "param"}'``처럼 실행하며, 이 명령은 지정된 dag_id의 시작~종료 날짜 사이 모든 인터벌에 대해 인스턴스를 다시 실행한다.
핵심 포인트
- Backfill은 catchup과 달리 사용자가 UI/CLI로 명시적인 과거 날짜 범위를 지정해 다시 실행을 트리거하는 절차다
- airflow backfill create CLI 명령으로 --from-date/--to-date/--reprocess-behavior/--max-active-runs/--run-backwards 등을 지정할 수 있다
태스크 재실행(Clear)과 태스크 인스턴스 히스토리
스케줄된 실행 중 일부 태스크가 실패할 수 있다. 로그를 보고 오류를 수정한 뒤에는 예약된 날짜에 대해 태스크를 클리어(clear)함으로써 재실행할 수 있다. 태스크 인스턴스를 클리어하면 그 태스크 인스턴스의 기록이 만들어지고, 현재 태스크 인스턴스의 ``try_number``는 증가하며, ``max_tries``는 0으로 설정되고, 상태는 ``None``으로 바뀌어 태스크가 다시 실행되게 된다. Airflow 3.1.0의 실험적 기능으로는 태스크 인스턴스를 클리어하면서 최신 번들 버전으로 재실행할 수 있는 옵션도 있다.
Tree나 Graph 뷰에서 실패한 태스크를 클릭하고 Clear를 클릭하면 익스큐터가 이를 다시 실행한다. 재실행 시 선택할 수 있는 옵션은 다음과 같다: Past(Dag의 가장 최근 데이터 인터벌 이전 실행들의 모든 인스턴스), Future(가장 최근 데이터 인터벌 이후 실행들의 모든 인스턴스), Upstream(현재 Dag 안의 업스트림 태스크들), Downstream(현재 Dag 안의 다운스트림 태스크들), Recursive(자식·부모 Dag의 모든 태스크), Failed(가장 최근 실행에서 실패한 태스크만). CLI로는 ``airflow tasks clear dag_id --task-regex task_regex --start-date START_DATE --end-date END_DATE``로 지정된 dag_id와 기간에 대해 정규식에 맞는 모든 태스크 인스턴스를 클리어할 수 있다.
태스크 인스턴스가 재시도되거나 클리어될 때 태스크 인스턴스 히스토리는 보존되며, Grid 뷰에서 태스크 인스턴스를 클릭해 이 히스토리를 볼 수 있다. try 선택기는 재시도되었거나 클리어된 태스크에만 표시된다. 히스토리는 각 실행이 끝났을 때의 태스크 인스턴스 속성 값을 보여주며, 로그 페이지에서 각 시도(try)별 로그도 볼 수 있어 디버깅에 유용하다. 다만 XCom이나 렌더링된 템플릿 필드 같은 관련 객체는 히스토리에 보존되지 않고, 로그를 포함한 태스크 인스턴스 속성만 보존된다.
핵심 포인트
- 태스크를 클리어하면 try_number가 증가하고 max_tries가 0, 상태가 None으로 바뀌어 재실행된다
- 재실행 옵션은 Past/Future/Upstream/Downstream/Recursive/Failed 여섯 가지이며 각각 대상 범위가 다르다
- 태스크 인스턴스 히스토리는 재시도/클리어 시 보존되어 시도별 로그와 속성값을 볼 수 있지만, XCom·렌더링된 템플릿 필드는 히스토리에 보존되지 않는다
외부 트리거, 파라미터 전달, Dag Run 대기
Dag Run은 CLI로도 수동 생성할 수 있다 — ``airflow dags trigger --logical-date logical_date run_id``. 이렇게 스케줄러 외부에서 생성된 Dag Run은 트리거된 시점의 타임스탬프와 연관되며 UI에서 스케줄된 Dag Run과 나란히 표시된다. logical date는 ``-e`` 인자로 지정할 수 있고, 기본값은 UTC 기준 현재 날짜다. 웹 UI(Dags 탭 → Links 컬럼 → Trigger Dag 버튼)로도 수동 트리거할 수 있다.
CLI, REST API, UI로 Dag를 트리거할 때 JSON blob 형태로 Dag Run에 대한 설정(config)을 전달할 수 있다. 예를 들어 BashOperator의 ``env``에 ``{{ dag_run.conf["message"] if dag_run else "" }}``처럼 Jinja로 ``dag_run.conf``를 참조할 수 있으며, ``dag_run.conf``의 파라미터는 오퍼레이터의 템플릿 필드 안에서만 사용할 수 있다.
Airflow는 Dag Run이 완료될 때까지 폴링으로 대기하는 실험적 API도 제공한다 — 이는 외부 시스템/자동화 파이프라인이 Dag가 끝날 때까지 실행을 멈추고 기다려야 할 때 유용하다. 이 엔드포인트는 지정된 Dag Run이 success, failed, canceled 중 하나의 터미널 상태에 도달할 때까지 블록되며, NDJSON(줄바꿈으로 구분된 JSON) 형식으로 응답을 스트리밍한다 — 각 줄이 그 시점의 Dag Run 상태를 나타내는 JSON 객체다. 이를 통해 클라이언트는 실시간으로 실행을 모니터링하고 선택적으로 특정 태스크의 XCom 결과를 함께 수집할 수 있다. 이 기능은 실험적이며 향후 버전에서 변경되거나 제거될 수 있다.
마지막으로, UI를 통해 태스크 인스턴스를 failed로 표시(실행 중인 태스크를 멈추는 용도)하거나 successful로 표시(허위 음성을 고치거나 Airflow 밖에서 이미 수정이 적용된 경우)할 수 있다.
핵심 포인트
- airflow dags trigger CLI나 UI의 Trigger Dag 버튼으로 Dag Run을 수동 생성할 수 있으며, JSON 설정(conf)을 함께 전달할 수 있다
- dag_run.conf의 파라미터는 오퍼레이터의 템플릿 필드 안에서만 참조할 수 있다
- Dag Run 완료 대기 API는 실험적 기능으로 NDJSON 스트리밍으로 success/failed/canceled 터미널 상태까지 폴링하며 대기한다