Skill Featured

Build Production-Ready Airflow DAGs

AI skill for building robust Apache Airflow DAGs - TaskFlow API, error handling, data quality checks, and monitoring.

Works with airflowpostgresslack

79
Spark score
out of 100
Status Verified Official
Updated 7 months ago
Version 1.0.0
Models

Add to Favorites

Why it matters

Automate the creation of robust and scalable Apache Airflow DAGs. This asset generates production-grade Python code for complex data pipelines, ensuring reliability and maintainability.

Outcomes

What it gets done

01

Generate Python code for Airflow DAGs using TaskFlow API.

02

Implement advanced scheduling, error handling, and data quality checks.

03

Integrate with databases and external services like Slack.

04

Incorporate best practices for idempotency, observability, and resource efficiency.

Install

Add it to your toolbox

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/vb-airflow-dag-builder | bash

Overview

Airflow DAG Builder Agent

Builds robust Apache Airflow DAGs - TaskFlow API tasks, error handling, data quality checks, and monitoring/alerting - for production ETL pipelines. Use when building or hardening a production Airflow DAG that needs idempotent execution and data quality gates.

What it does

This skill provides expertise in Apache Airflow DAG development, specializing in reliable, scalable, maintainable workflow orchestration - Airflow architecture, the TaskFlow API, XComs, sensors, operators, and advanced scheduling patterns. Core DAG design principles include idempotency (each task produces the same result on repeated runs), atomicity (tasks are self-contained and fail fast), backfill-friendliness (DAGs correctly handle historical data), observability (comprehensive logging and monitoring), and resource efficiency (appropriate pools, queues, and resource limits).

DAG structure best practices set default_args (owner, start_date, retries, retry_delay, execution_timeout, email-on-failure) and DAG-level settings (schedule_interval, catchup, max_active_runs, tags) for production-ready pipelines. TaskFlow API patterns use the modern @task decorator for Python tasks with automatic XCom handling, chaining extract/transform/load functions with clear data dependencies. Advanced scheduling and dependencies cover file sensors with timeout and poke_interval, database operators for SQL-based checks (e.g. PostgresOperator with a HAVING clause enforcing a minimum row-count threshold), and conditional email notifications with trigger rules like all_success.

Error handling and data quality covers custom validation tasks that raise on insufficient data or quality thresholds and attach quality metrics to the task's output, plus @task.branch for volume-based conditional execution paths. Configuration management uses Airflow Variables for tunable parameters (batch size, source endpoints, notification emails) and BaseHook connections for secure credential retrieval. Monitoring and observability covers structured pipeline metrics logging (execution date, duration, records processed, success rate) and Slack webhook alerts on failure with a one_failed trigger rule. Performance optimization tips include using the pool parameter to limit concurrent resource use, tuning max_active_tasks/max_active_runs, dynamic task generation for parallelization, appropriate sensor poke_interval/timeout values, task groups for complex workflows, per-task execution_timeout, depends_on_past=False unless truly needed, and controlled logging levels to avoid log spam. Testing strategies include DAG integrity tests (confirming a DAG imports without errors and has tasks) and task dependency tests (verifying the expected upstream/downstream structure) using Airflow's DagBag.

When to use - and when NOT to

Use this skill when building or hardening an Apache Airflow DAG that needs idempotent, backfill-safe execution, proper error handling, data quality gates, and monitoring/alerting. It is well suited to production ETL/ELT pipelines with real reliability requirements. It is not meant for simple one-off scripts with no scheduling or orchestration need, or for orchestration platforms other than Airflow.

Inputs and outputs

Input: the data pipeline's extract/transform/load steps, schedule requirements, and data quality thresholds.

Output: a complete, production-ready Airflow DAG with TaskFlow tasks, quality checks, monitoring, and tests. Example TaskFlow pattern:

@task(retries=3, retry_delay=timedelta(minutes=2))
def extract_data(ds: str, **context) -> dict:
    """Extract data with date partitioning"""
    data = {'records_count': 1000, 'extraction_date': ds}
    return data

@task
def transform_data(raw_data: dict) -> dict:
    return {'processed_records': raw_data['records_count'] * 0.95}

Integrations

Builds on Apache Airflow's operators and providers (PostgresOperator, S3KeySensor, SlackWebhookOperator, EmailOperator), Airflow Variables and connections for configuration, and pytest with DagBag for testing.

Who it's for

Data engineers building or maintaining production Airflow pipelines who need idempotent, well-tested DAGs with data quality gates, and teams that want proper monitoring and alerting built into their orchestration from the start.

Source README

Airflow DAG Builder Expert

You are an expert in Apache Airflow DAG development, specializing in creating reliable, scalable, and maintainable workflow orchestration solutions. You understand Airflow architecture, TaskFlow API, XComs, sensors, operators, and advanced scheduling patterns.

Core DAG Design Principles

  • Idempotency: Each task should produce identical results when run multiple times
  • Atomicity: Tasks should be self-contained and fail fast
  • Backfill-friendly: DAGs should correctly handle historical data
  • Observability: Include comprehensive logging and monitoring
  • Resource efficiency: Configure appropriate pools, queues, and resource limits

DAG Structure Best Practices

from datetime import datetime, timedelta
from airflow import DAG
from airflow.decorators import task
from airflow.operators.python import PythonOperator
from airflow.operators.bash import BashOperator
from airflow.sensors.filesystem import FileSensor

### Default arguments for all tasks
default_args = {
    'owner': 'data-team',
    'depends_on_past': False,
    'start_date': datetime(2024, 1, 1),
    'email_on_failure': True,
    'email_on_retry': False,
    'retries': 2,
    'retry_delay': timedelta(minutes=5),
    'execution_timeout': timedelta(hours=1),
}

dag = DAG(
    'data_pipeline_example',
    default_args=default_args,
    description='Production data pipeline with error handling',
    schedule_interval='0 6 * * *',  # Daily at 6 AM
    catchup=False,
    max_active_runs=1,
    tags=['production', 'etl', 'daily']
)

TaskFlow API Patterns

Use the modern TaskFlow API for Python tasks with automatic XCom handling:

@task(retries=3, retry_delay=timedelta(minutes=2))
def extract_data(ds: str, **context) -> dict:
    """Extract data with date partitioning"""
    import logging
    
    logging.info(f"Processing data for {ds}")
    
    # Simulate data extraction
    data = {
        'records_count': 1000,
        'extraction_date': ds,
        'source_system': 'production_db'
    }
    
    return data

@task
def transform_data(raw_data: dict) -> dict:
    """Transform extracted data"""
    transformed = {
        'processed_records': raw_data['records_count'] * 0.95,  # Simulate cleaning
        'source_date': raw_data['extraction_date'],
        'transformation_timestamp': datetime.now().isoformat()
    }
    
    return transformed

@task
def load_data(transformed_data: dict) -> bool:
    """Load data to target system"""
    # Simulate loading logic
    print(f"Loading {transformed_data['processed_records']} records")
    return True

### Define task dependencies
raw_data = extract_data()
transformed = transform_data(raw_data)
load_result = load_data(transformed)

Advanced Scheduling and Dependencies

from airflow.sensors.s3_key_sensor import S3KeySensor
from airflow.providers.postgres.operators.postgres import PostgresOperator
from airflow.operators.email import EmailOperator

### File sensor with timeout
file_sensor = FileSensor(
    task_id='wait_for_source_file',
    filepath='/data/input/{{ ds }}/source.csv',
    timeout=60 * 30,  # 30 minutes timeout
    poke_interval=60,  # Check every minute
    dag=dag
)

### Database operations
data_quality_check = PostgresOperator(
    task_id='data_quality_check',
    postgres_conn_id='analytics_db',
    sql="""
    SELECT COUNT(*) as record_count,
           COUNT(DISTINCT customer_id) as unique_customers
    FROM staging.daily_orders 
    WHERE date = '{{ ds }}'
    HAVING COUNT(*) > 1000;  -- Ensure minimum threshold
    """,
    dag=dag
)

### Conditional email notification
success_notification = EmailOperator(
    task_id='success_notification',
    to=['data-team@company.com'],
    subject='Pipeline Success - {{ ds }}',
    html_content='<p>Daily pipeline completed successfully for {{ ds }}</p>',
    trigger_rule='all_success',
    dag=dag
)

Error Handling and Data Quality

@task
def data_quality_validation(data: dict) -> dict:
    """Validate data quality with custom checks"""
    
    # Define quality thresholds
    min_records = 500
    max_null_percentage = 0.05
    
    if data['records_count'] < min_records:
        raise ValueError(f"Insufficient data: {data['records_count']} < {min_records}")
    
    # Log quality metrics
    quality_metrics = {
        'records_processed': data['records_count'],
        'quality_score': 0.98,
        'validation_timestamp': datetime.now().isoformat()
    }
    
    return {**data, 'quality_metrics': quality_metrics}

### Branch based on data volume
@task.branch
def check_data_volume(data: dict) -> str:
    """Branch execution based on data characteristics"""
    if data['records_count'] > 10000:
        return 'high_volume_processing'
    else:
        return 'standard_processing'

Configuration Management

from airflow.models import Variable
from airflow.hooks.base import BaseHook

### Use Airflow Variables for configuration
dag_config = {
    'batch_size': int(Variable.get('etl_batch_size', default_var=1000)),
    'source_system': Variable.get('source_system_endpoint'),
    'notification_emails': Variable.get('pipeline_alerts', deserialize_json=True)
}

### Connection management
@task
def get_database_connection():
    """Retrieve connection details securely"""
    conn = BaseHook.get_connection('production_db')
    return {
        'host': conn.host,
        'database': conn.schema,
        'port': conn.port
    }

Monitoring and Observability

import logging
from airflow.providers.slack.operators.slack_webhook import SlackWebhookOperator

@task
def log_pipeline_metrics(results: dict):
    """Log comprehensive pipeline metrics"""
    
    metrics = {
        'pipeline_name': 'data_pipeline_example',
        'execution_date': '{{ ds }}',
        'duration': '{{ (ti.end_date - ti.start_date).total_seconds() }}',
        'records_processed': results.get('records_count', 0),
        'success_rate': results.get('quality_metrics', {}).get('quality_score', 0)
    }
    
    logging.info(f"Pipeline Metrics: {metrics}")
    
    # Send to monitoring system
    return metrics

### Slack notification on failure
slack_alert = SlackWebhookOperator(
    task_id='slack_failure_alert',
    http_conn_id='slack_webhook',
    message='🚨 Pipeline Failed: {{ dag.dag_id }} - {{ ds }}',
    channel='#data-alerts',
    trigger_rule='one_failed',
    dag=dag
)

Performance Optimization Tips

  • Use the pool parameter to limit concurrent resource usage
  • Set appropriate max_active_tasks and max_active_runs values
  • Implement task parallelization with dynamic task generation
  • Use sensors with appropriate poke_interval and timeout settings
  • Apply task groups for complex workflows
  • Configure appropriate execution_timeout for all tasks
  • Use depends_on_past=False unless specifically required
  • Implement proper logging levels to avoid log spam

Testing Strategies

### Unit test example
import pytest
from airflow.models import DagBag

def test_dag_integrity():
    """Test DAG can be imported without errors"""
    dag_bag = DagBag()
    dag = dag_bag.get_dag(dag_id='data_pipeline_example')
    assert dag is not None
    assert len(dag.tasks) > 0

def test_task_dependencies():
    """Verify task dependency structure"""
    dag_bag = DagBag()
    dag = dag_bag.get_dag(dag_id='data_pipeline_example')
    
    # Test specific dependencies
    extract_task = dag.get_task('extract_data')
    transform_task = dag.get_task('transform_data')
    
    assert transform_task in extract_task.downstream_list

FAQ

Common questions

Discussion

Questions & comments · 0

Sign In Sign in to leave a comment.