← Back to Data Engineering
Data Engineering

Designing Dynamic, Scalable Airflow DAGs at Enterprise Scale


In a rapidly growing data organization, writing a separate Python file for every single ETL/ELT pipeline is an anti-pattern. If you have 500 tables to ingest from a MySQL database, manual DAG creation leads to massive code duplication, inconsistent standards, and maintenance nightmares.

As a Staff Data Engineer, your job is to build systems that automate the generation of these workflows. In this article, we’ll explore how to dynamically build Airflow DAGs using configuration files.


1. The Dynamic DAG Pattern

Airflow parses the dags/ folder periodically looking for any DAG objects defined at the global scope of a Python file. We can leverage this behavior to write a single Python script that reads a set of configurations (YAML, JSON, or database rows) and registers multiple DAGs dynamically.


2. Setting Up the Configuration Layout

Let’s define our pipelines in clean YAML files. Create a directory structure like this:

dags/
├── config/
│   ├── customer_ingestion.yaml
│   └── order_ingestion.yaml
└── dynamic_dag_generator.py

Example Config: customer_ingestion.yaml

dag_id: "ingest_mysql_customers"
schedule: "0 2 * * *"
owner: "finance_team"
source_table: "production_customers"
target_table: "lakehouse.raw_customers"
retries: 3

3. The Generator Script (dynamic_dag_generator.py)

Now, write the Python generator that scans the config/ directory, parses each YAML file, and registers a unique DAG.

import os
import yaml
from datetime import datetime, timedelta
from airflow import DAG
from airflow.providers.standard.operators.bash import BashOperator

# Path to the configurations
CONFIG_DIR = os.path.join(os.path.dirname(__file__), 'config')

def create_dag(config):
    default_args = {
        'owner': config.get('owner', 'data_platform'),
        'start_date': datetime(2026, 1, 1),
        'retries': config.get('retries', 1),
        'retry_delay': timedelta(minutes=5),
    }

    dag = DAG(
        dag_id=config['dag_id'],
        default_args=default_args,
        schedule=config.get('schedule', '@daily'),
        catchup=False,
        tags=['dynamic', 'ingestion']
    )

    with dag:
        # Define tasks dynamically using configuration settings
        start_task = BashOperator(
            task_id='start_signal',
            bash_command=f"echo 'Starting ingestion for {config['source_table']}'"
        )

        run_ingest = BashOperator(
            task_id='ingest_data',
            bash_command=f"python /opt/scripts/ingest.py --src {config['source_table']} --dest {config['target_table']}"
        )

        end_task = BashOperator(
            task_id='end_signal',
            bash_command="echo 'Ingestion completed successfully!'"
        )

        start_task >> run_ingest >> end_task

    return dag

# Scan config directory and register DAGs in the global scope
for file_name in os.listdir(CONFIG_DIR):
    if file_name.endswith('.yaml') or file_name.endswith('.yml'):
        config_path = os.path.join(CONFIG_DIR, file_name)
        with open(config_path, 'r') as f:
            config_data = yaml.safe_load(f)
            
            # Airflow requires the DAG object to be in the globals() dictionary
            globals()[config_data['dag_id']] = create_dag(config_data)

4. Crucial Pitfalls & Performance Tips

While dynamic DAGs are powerful, you must follow these production rules:

  1. Avoid External Database Calls during Parsing: The DAG folder is parsed every few seconds by the scheduler. Placing database queries or heavy API requests at the root level of your generator script will exhaust connection pools and bring your Airflow scheduler to its knees.
  2. Keep File Operations Quick: Reading local YAML files is fast, but if you have thousands of configurations, keep an eye on parsing latency.
  3. Use Stable Start Dates: Never use datetime.now() for a start date in Airflow. It causes erratic scheduling runs and prevents historical backfills. Use a fixed date like datetime(2026, 1, 1).

By moving pipeline details into configs, you enable business analysts and junior engineers to write and deploy new pipelines without touching Python code!