Airflow Trigger Rules

Airflow의 기본 동작은 모든 상위 Task가 성공해야 해당 Task를 실행하는 것입니다. 하지만 trigger_rule 매개변수를 사용하면 이러한 기본 동작을 다양한 방식으로 변경할 수 있어, 복잡한 워크플로우 시나리오에 대응할 수 있다.

즉시 실행 계열: one_failed, one_success, one_done은 모든 상위 Task의 완료를 기다리지 않고 조건이 충족되는 즉시 실행됩니다. 예를 들어 one_failed는 첫 번째 실패가 감지되면 바로 에러 처리 Task를 시작하므로 빠른 대응이 가능하다.

완료 기반 계열: all_success, all_failed, all_done, all_skipped는 모든 상위 Task의 완료를 기다린 후 해당 상태를 확인하여 실행 여부를 결정합니다. all_done은 성공/실패와 관계없이 모든 Task가 완료되면 실행되므로 정리 작업에 유용하다.

조건부 실행 계열: none_failed, none_failed_min_one_success, none_skipped는 특정 상태가 없을 때 실행되는 조건들로, 분기 워크플로우에서 특히 중요합니다. none_failed_min_one_success는 실패가 없으면서 최소 하나는 성공한 경우에만 실행되어, 분기 후 결과 수집에 가장 적합하다.

trigger_rule의 옵션들은 다음과 같다:

  • all_success (기본값): 모든 상위 Task가 성공했을 때
  • all_failed: 모든 상위 Task가 failed 또는 upstream_failed 상태일 때
  • all_done: 모든 상위 Task의 실행이 완료되었을 때
  • all_skipped: 모든 상위 Task가 skipped 상태일 때
  • one_failed: 최소 하나의 상위 Task가 실패했을 때 (모든 상위 Task의 완료를 기다리지 않음)
  • one_success: 최소 하나의 상위 Task가 성공했을 때 (모든 상위 Task의 완료를 기다리지 않음)
  • one_done: 최소 하나의 상위 Task가 성공하거나 실패했을 때
  • none_failed: 모든 상위 Task가 failed 또는 upstream_failed 상태가 아닐 때 - 즉, 모든 상위 Task가 성공하거나 건너뛰어졌을 때
  • none_failed_min_one_success: 모든 상위 Task가 failed 또는 upstream_failed 상태가 아니면서, 최소 하나의 상위 Task가 성공했을 때
  • none_skipped: skipped 상태인 상위 Task가 없을 때 - 즉, 모든 상위 Task가 success, failed, 또는 upstream_failed 상태일 때
  • always: 의존성과 관계없이 언제든지 이 Task를 실행
import logging
from datetime import datetime, timedelta

from airflow.decorators import dag, task
from airflow.exceptions import AirflowSkipException, AirflowFailException
from airflow.utils.trigger_rule import TriggerRule

# Default DAG arguments
_default_args = {
    "owner": "MMIX",
    "start_date": datetime(1970, 1, 1),
    "retries": 1,
    "retry_delay": timedelta(seconds=5)
}


# Define the DAG
@dag(
    dag_id="example_trigger_rule",
    default_args=_default_args,
    schedule_interval=None,
    catchup=False,
    description="An example DAG demonstrating trigger rules in Airflow",
    tags=["Example", "Trigger", "Rule", "Dag", "Task"]
)
def example_trigger():
    logging.info("Trigger Rule Example DAG started")

    @task
    def start() -> str:
        logging.info("Start Task executed")
        return "Start Task completed"

    @task(task_id="success_task")
    def success() -> str:
        logging.info("Success Task executed")
        return "Success Task completed"

    @task(task_id="failed_task")
    def failed() -> None:
        logging.info("Failed Task executed, this will raise an exception")
        raise AirflowFailException("This task is intentionally failed to demonstrate trigger rules")

    @task(task_id="skipped_task")
    def skipped() -> None:
        logging.info("Skip Task executed, this will skip the task")
        raise AirflowSkipException("This task is intentionally skipped to demonstrate trigger rules")

    @task(task_id="end_task", trigger_rule=TriggerRule.ALWAYS)
    def end() -> str:
        logging.info("End Task executed")
        return "End Task completed"

    def create_logging_task(task_id: str, trigger_rule: str):
        @task(task_id=task_id, trigger_rule=trigger_rule)
        def _task():
            logging.info(f"{task_id} executed.")

        return _task()

    start_operation = start()
    success_operation = success()
    failed_operation = failed()
    skipped_operation = skipped()
    end_operation = end()

    trigger_rule_tasks = {
        "all_success_task": TriggerRule.ALL_SUCCESS,
        "all_failed_task": TriggerRule.ALL_FAILED,
        "all_skipped_task": TriggerRule.ALL_SKIPPED,
        "all_done_task": TriggerRule.ALL_DONE,
        "one_failed_task": TriggerRule.ONE_FAILED,
        "one_success_task": TriggerRule.ONE_SUCCESS,
        "one_done_task": TriggerRule.ONE_DONE,
        "none_failed_task": TriggerRule.NONE_FAILED,
        "none_failed_min_one_success_task": TriggerRule.NONE_FAILED_MIN_ONE_SUCCESS,
        "none_skipped_task": TriggerRule.NONE_SKIPPED,
    }
    created_operations = {
        task_id: create_logging_task(task_id, rule)
        for task_id, rule in trigger_rule_tasks.items()
    }

    branches = [success_operation, failed_operation, skipped_operation]
    for branch in branches:
        start_operation >> branch

    for operation in created_operations.values():
        for branch in branches:
            branch >> operation

    for task_id, operation in created_operations.items():
        if task_id != "all_done_task":
            start_operation >> operation
            operation >> end_operation

    created_operations["all_done_task"] >> end_operation


example_trigger()

이 Airflow DAG는 Trigger Rules의 모든 유형을 체계적으로 학습할 수 있도록 설계된 종합적인 실습 예제로, start Task를 시작점으로 하여 의도적으로 성공(success), 실패(failed), 스킵(skipped) 상태를 만드는 세 개의 기본 Task를 AirflowFailException과 AirflowSkipException을 사용해 생성합니다.

create_logging_task 함수를 통해 ALL_SUCCESS, ALL_FAILED, ONE_SUCCESS, ONE_FAILED, NONE_FAILED_MIN_ONE_SUCCESS 등 10가지 서로 다른 Trigger Rule을 가진 Task들을 동적으로 생성하며, 각 Trigger Rule Task는 앞서 만든 성공/실패/스킵 Task들을 모두 상위 Task로 받아 자신의 실행 조건에 따라 동작합니다.

워크플로우의 구조는 start → [success, failed, skipped] → [각종 trigger rule tasks] → end 순서로 진행되며, 이를 통해 각 Trigger Rule이 어떤 상위 Task 조건에서 실행되고 어떤 조건에서 건너뛰어지는지 실제 로그를 통해 확인할 수 있습니다.

마지막 end Task는 TriggerRule.ALWAYS로 설정되어 모든 상위 Task들의 상태와 관계없이 항상 실행되어 워크플로우의 완전한 종료를 보장하며, 이러한 구조를 통해 복잡한 분기 워크플로우나 에러 처리 시나리오에서 활용할 수 있는 Trigger Rules의 실제 동작 방식을 체계적으로 학습할 수 있도록 구성된 교육용 DAG입니다.

Trigger Rule Graph