Datadog Airflow Observability

Quote

“Do you know what’s better than debugging at 3 AM with really great tools? Not having to wake up at 3 AM.”

Liz Fong-Jones

How StatsD Metrics Flow from Airflow to Datadog

Airflow emits internal metrics via StatsD — a lightweight protocol that sends UDP packets with metric names and values. The Datadog agent receives these on port 8125 and forwards them to the Datadog platform.

┌──────────────────┐     StatsD (UDP:8125)     ┌──────────────┐     HTTPS     ┌─────────┐
│ airflow-scheduler │ ──────────────────────────► │   dd-agent   │ ────────────► │ Datadog │
│ airflow-webserver │                            │ (port 8125)  │              │  Cloud  │
│ airflow-triggerer │                            └──────────────┘              └─────────┘
└──────────────────┘
    All on airflow-net Docker network

Enabling StatsD in Airflow Docker Compose

Add these environment variables to the shared AIRFLOW_ENV array in infra/scripts/airflow-startup.sh:

AIRFLOW_ENV=(
  # ... existing variables ...
  -e AIRFLOW__METRICS__STATSD_ON=True
  -e AIRFLOW__METRICS__STATSD_HOST=dd-agent
  -e AIRFLOW__METRICS__STATSD_PORT=8125
  -e AIRFLOW__METRICS__STATSD_PREFIX=airflow
)
VariableValuePurpose
STATSD_ONTrueEnables StatsD metric emission from Airflow
STATSD_HOSTdd-agentDocker container name of the Datadog agent (resolved via airflow-net network)
STATSD_PORT8125Default DogStatsD port on the Datadog agent
STATSD_PREFIXairflowAll metrics are prefixed with airflow. (e.g., airflow.dagrun.duration.success)

The Datadog agent must have DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true (already set in the startup script) to accept StatsD from other containers.


Deploying the StatsD Configuration Change

After modifying the startup script locally:

# 1. Push updated script to VM metadata
gcloud compute instances add-metadata data-pipeline-airflow --zone=europe-west1-b \
  --metadata-from-file startup-script=infra/scripts/airflow-startup.sh
 
# 2. SSH into the VM
gcloud compute ssh data-pipeline-airflow --zone=europe-west1-b --tunnel-through-iap
 
# 3. Force-recreate containers (the startup script skips running containers)
sudo docker rm -f airflow-scheduler airflow-webserver airflow-triggerer
 
# 4. Re-run the startup script from metadata
curl -sf -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/attributes/startup-script" | sudo bash

Verifying Metrics Flow

After the containers restart:

sudo docker exec dd-agent agent status | grep -A 10 DogStatsD

Expected output:

DogStatsD
=========
  Metric Packets: 3,451        ← metrics flowing
  Metric Parse Errors: 0
  Udp Bytes: 390,196
  Udp Packet Reading Errors: 0

Then in Datadog: Metrics → Summary → search airflow to confirm metrics are ingested.

Enabling the Airflow Integration in Datadog

Go to Integrations → Airflow → Install. This activates the default Airflow dashboard and metric parsing rules.

Data Observability Limitation

Datadog’s “Data Observability” product (monitoring data quality, freshness, schema changes) only supports managed Airflow platforms (Cloud Composer, MWAA, Astronomer, Kubernetes). For self-hosted Airflow on a GCE VM, the StatsD integration described here is the standard approach.


Key Metrics Reference

Airflow 2.10 embeds the DAG ID and task ID directly in the metric name (not as tags). The naming pattern is:

airflow.<category>.<dag_id>.<task_id>.<metric>.<aggregation>

Scheduler Health

MetricTypeWhat It Tells You
airflow.scheduler_heartbeatCounterIs the scheduler alive? Stops incrementing if scheduler is dead
airflow.scheduler.scheduler_loop_duration.avgGaugeHow long each scheduler loop takes (µs). High values = scheduler overloaded
airflow.scheduler.tasks.executableGaugeNumber of tasks ready to be sent to the executor
airflow.scheduler.tasks.starvingGaugeTasks that can’t run because all pool slots are full
airflow.scheduler.orphaned_tasks.adoptedCounterTasks recovered after a scheduler restart
airflow.dag_processing.total_parse_timeGaugeTotal time to parse all DAG files (seconds). Spikes indicate broken DAG files
airflow.dag_processing.import_errorsGaugeNumber of DAG files with import errors
airflow.dagbag_sizeGaugeTotal number of DAGs loaded

DAG Run Performance

MetricTypeWhat It Tells You
airflow.dagrun.duration.success.pipeline_pulse.avgGaugeAverage successful run duration for pipeline_pulse (µs)
airflow.dagrun.duration.success.pipeline_tickers.avgGaugeAverage successful run duration for pipeline_tickers (µs)
airflow.dagrun.schedule_delay.pipeline_pulse.avgGaugeTime between scheduled run time and actual start (µs). High = scheduler backlog
airflow.dagrun.dependency_check.pipeline_pulse.avgGaugeTime spent checking task dependencies (µs)
airflow.dagrun.pipeline_pulse.first_task_scheduling_delay.avgGaugeDelay before the first task in a DAG run starts

Task Execution

MetricTypeWhat It Tells You
airflow.dag.pipeline_pulse.fetch_and_load_pulse.duration.avgGaugeAverage task execution time (µs)
airflow.dag.pipeline_pulse.fetch_and_load_pulse.queued_duration.avgGaugeTime spent waiting in the queue before execution (µs)
airflow.ti.finish.pipeline_pulse.fetch_and_load_pulse.successCounterNumber of successful task completions
airflow.ti.finish.pipeline_pulse.fetch_and_load_pulse.failedCounterNumber of failed task completions
airflow.ti_successesCounterTotal task successes across all DAGs
airflow.operator_successes_CloudRunExecuteJobOperatorCounterSuccesses by operator type
airflow.task.cpu_usage.pipeline_pulse.fetch_and_load_pulseGaugeCPU usage of the task process
airflow.task.mem_usage.pipeline_pulse.fetch_and_load_pulseGaugeMemory usage of the task process

Pool and Executor

MetricTypeWhat It Tells You
airflow.executor.open_slotsGaugeAvailable executor capacity
airflow.executor.running_tasksGaugeCurrently executing tasks
airflow.executor.queued_tasksGaugeTasks waiting for an executor slot
airflow.pool.open_slots.default_poolGaugeAvailable slots in the default pool
airflow.pool.running_slots.default_poolGaugeOccupied slots in the default pool
airflow.pool.starving_tasks.default_poolGaugeTasks that can’t run because the pool is full

Triggerer

MetricTypeWhat It Tells You
airflow.triggerer_heartbeatCounterIs the triggerer alive?
airflow.triggers.runningGaugeNumber of active triggers (deferrable operators waiting)
airflow.triggers.blocked_main_threadCounterTriggers that blocked the async event loop (performance issue)

Airflow Orchestration Dashboard in Datadog

A custom dashboard definition is stored at infra/datadog/airflow_dashboard.json. To import it:

  1. In Datadog → Dashboards → New Dashboard → name it “Airflow” → click New Dashboard (grid layout)
  2. Inside the dashboard, click the gear icon → Import Dashboard JSON
  3. Paste the contents of infra/datadog/airflow_dashboard.json

The dashboard contains 11 widgets:

WidgetTypeMetric(s)
Scheduler HeartbeatCounterairflow.scheduler_heartbeat
DAG Bag SizeCounterairflow.dagbag_size
Running TasksCounterairflow.executor.running_tasks
Open SlotsCounterairflow.executor.open_slots
Successes vs FailuresBar chartairflow.ti.finish.*.success / *.failed
DAG Run DurationLine chartairflow.dagrun.duration.success.*.avg per DAG
Task Queued Duration (p95)Line chartairflow.dag.*.queued_duration.95percentile
DAG Parse TimeLine chartairflow.dag_processing.total_parse_time
Pool SlotsArea chartairflow.pool.open_slots / used_slots
Scheduler LoopLine chartairflow.scheduler.tasks.executable / starving / loop_duration
Container LogsLog streamLive logs from scheduler, webserver, triggerer

Metric Units

Airflow emits durations in microseconds. To display as seconds in Datadog, edit the widget → Y-axis → set unit to microsecond and Datadog auto-formats (e.g., 56,000,000 µs → 56s).


Create these in Monitors → New Monitor → Metric:

MonitorMetricConditionSeverity
Scheduler Downairflow.scheduler_heartbeatNo data for 5 minP1 (Critical)
DAG Parse Errorairflow.dag_processing.import_errors> 0 for 5 minP2 (High)
Task Failureairflow.ti.finish.*.failed> 0 within 15 minP2 (High)
Pool Starvationairflow.pool.starving_tasks.default_pool> 0 for 10 minP3 (Medium)
DAG Duration Anomalyairflow.dagrun.duration.success.pipeline_pulse.avg> 2x baseline for 3 consecutive runsP3 (Medium)
Queued Duration Spikeairflow.dag.*.queued_duration.95percentile> 60,000,000 (60s)P4 (Low)
Triggerer Downairflow.triggerer_heartbeatNo data for 5 minP3 (Medium)

Limitations of Self-Hosted Airflow Observability

Related pattern

When Datadog metrics reveal task failures or scheduler anomalies, the airflow-troubleshooting guide provides targeted diagnostic steps for common failure modes like import errors, pool exhaustion, and zombie task recovery.

CapabilityAvailable?Alternative
StatsD metrics (DAG runs, tasks, scheduler)Yes
Container logs (scheduler, webserver, triggerer)Yes
APM traces for DAG tasksNo (tasks run on Cloud Run, not on the Airflow VM)Instrument the pipeline container with ddtrace
Data Observability (row counts, freshness, schema)No (requires managed Airflow)Emit custom StatsD metrics from pipeline code
DAG code-level profilingNoUse Airflow’s built-in task duration metrics