Cross-DAG Dependencies in Apache Airflow
Connecting DAGs Without Breaking Your Platform πβ
The Story: When One DAG Isnβt Enoughβ
Imagine a growing data platform.
- DAG A ingests raw customer data
- DAG B transforms that data
- DAG C trains a machine learning model
Each DAG belongs to a different team.
Each DAG runs on a different schedule.
Now comes the big question:
How do you ensure DAG B only runs after DAG A finishes β and DAG C waits for both?
This challenge is known as Cross-DAG Dependencies, and handling it poorly is one of the fastest ways to create fragile, unreliable Airflow systems.
Letβs learn how to do it the right way.
What Are Cross-DAG Dependencies?β
A Cross-DAG Dependency exists when:
One DAG depends on the successful execution or output of another DAG.
Simple Exampleβ
- DAG
load_sales_datamust finish - Before DAG
generate_sales_reportstarts
Unlike task dependencies (task1 >> task2), these dependencies cross DAG boundaries.
Why Cross-DAG Dependencies Are Hardβ
Cross-DAG dependencies introduce complexity because:
- DAGs may have different schedules
- DAGs may run in different timezones
- Failures can cascade silently
- Backfills become harder
- Tight coupling hurts scalability
Thatβs why how you implement them matters.
Traditional Approach: ExternalTaskSensorβ
How It Worksβ
The ExternalTaskSensor waits for a task (or DAG) in another DAG to complete.
Example: ExternalTaskSensorβ
from airflow import DAG
from airflow.sensors.external_task import ExternalTaskSensor
from airflow.operators.python import PythonOperator
from datetime import datetime
def process_data():
print("Processing downstream data")
with DAG(
dag_id="downstream_dag",
start_date=datetime(2024, 1, 1),
schedule="@daily",
catchup=False
):
wait_for_upstream = ExternalTaskSensor(
task_id="wait_for_upstream_dag",
external_dag_id="upstream_dag",
external_task_id=None,
mode="reschedule"
)
process = PythonOperator(
task_id="process_data",
python_callable=process_data
)
wait_for_upstream >> process
Inputβ
| Input |
|---|
| Upstream DAG run completed |
Outputβ
| Output |
|---|
| Downstream DAG starts processing |
Problems with ExternalTaskSensor ββ
β Tight coupling between DAGs
β Breaks easily during backfills
β Sensor tasks occupy scheduler resources
β Hard to manage at scale
ExternalTaskSensor works β but it doesnβt scale well.
Modern Approach: Cross-DAG Dependencies with Datasets β β
Airflow Datasets are now the recommended way to manage Cross-DAG dependencies.
Instead of waiting on a DAG, you wait on data.
Dataset-Based Cross-DAG Dependency (Best Practice)β
Step 1: Define a Shared Datasetβ
from airflow.datasets import Dataset
customer_dataset = Dataset("warehouse.curated.customers")
Step 2: Producer DAG (Publishes Dataset)β
from airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime
def load_customers():
print("Customer data loaded")
with DAG(
dag_id="load_customers_dag",
start_date=datetime(2024, 1, 1),
schedule="@daily",
catchup=False
):
PythonOperator(
task_id="load_customers",
python_callable=load_customers,
outlets=[customer_dataset]
)
Step 3: Consumer DAG (Triggered by Dataset)β
from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow.datasets import Dataset
from datetime import datetime
customer_dataset = Dataset("warehouse.curated.customers")
def transform_customers():
print("Transforming customer data")
with DAG(
dag_id="transform_customers_dag",
start_date=datetime(2024, 1, 1),
schedule=[customer_dataset],
catchup=False
):
PythonOperator(
task_id="transform_customers",
python_callable=transform_customers
)
Inputβ
| Input |
|---|
Dataset warehouse.curated.customers updated |
Outputβ
| Output |
|---|
| Downstream DAG triggered automatically |
Why Datasets Are Better Than Sensorsβ
| Aspect | ExternalTaskSensor | Datasets |
|---|---|---|
| Coupling | Tight | Loose |
| Scalability | Poor | Excellent |
| Resource Usage | High | Minimal |
| Backfill Safety | Risky | Safe |
| Data Awareness | β No | β Yes |
Multi-DAG Dependency with Multiple Datasetsβ
A DAG can depend on multiple datasets.
with DAG(
dag_id="ml_training_dag",
start_date=datetime(2024, 1, 1),
schedule=[
Dataset("warehouse.curated.customers"),
Dataset("warehouse.curated.orders")
],
catchup=False
):
...
Inputβ
| Dataset |
|---|
| Customers updated |
| Orders updated |
Outputβ
| Output |
|---|
| ML training starts only when both are ready |
When ExternalTaskSensor Is Still Acceptableβ
Use it only if:
- You are on Airflow < 2.4
- You must wait for a specific task, not data
- Migration to datasets is not yet possible
If you use it:
β
Always use mode="reschedule"
β
Set timeouts
β
Avoid chaining sensors
Common Mistakes in Cross-DAG Dependenciesβ
β Making DAGs depend on DAG names instead of data
β Long sensor chains blocking workers
β Circular dependencies between DAGs
β Ignoring backfill behavior
Best Practices for Cross-DAG Dependenciesβ
β
Prefer Datasets over Sensors
β
Treat datasets as data contracts
β
Keep DAGs loosely coupled
β
Document dataset ownership
β
Design DAGs to be independently runnable
Summary π§ β
- Cross-DAG dependencies are unavoidable in real platforms.
- Traditional sensors work but donβt scale well.
- Airflow Datasets provide a modern, event-driven solution.
- Data-driven orchestration is more reliable than DAG-driven orchestration.
Key Takeawaysβ
- DAGs should depend on data, not other DAGs.
- Datasets reduce coupling and improve reliability.
- ExternalTaskSensor is a legacy tool β use sparingly.
- Modern Airflow architectures are dataset-first.
Whatβs Next?β
β‘οΈ Custom Plugins in Apache Airflow β Hooks, Operators, and Sensors