Reading the Graph and Grid

Concepts covered: Task dependencies

Graph: the shape of one run

The DAG page in the UI has 2 views of the same DAG, switched by a toggle. The graph draws the tasks as boxes and the edges as arrows, laid out left to right, so nightly_bookings shows get_bookings and get_reviews stacked on the left, build_report to their right with 2 arrows in, and publish_dashboard last. The shape alone answers 2 questions: which tasks can overlap, those with no arrow path between them, and what the longest chain is, which bounds how fast a run can finish. Select a run and each box takes the color of that task's state in that run, so the graph becomes a picture of where one night stands.

Grid: the history of every run

The grid turns time into columns. Each column is one run, oldest on the left and newest on the right; each row is one task; the cell where they meet is colored by the state of that task in that run. Read down a column to see how one night went; read across a row to see how one task has behaved night after night. 3 nights of bookings_report, the 3-task cousin of nightly_bookings that the animation below plays, make 3 columns of 3 cells. The colors are Airflow's state names, and 7 of them cover everything you will see in this lesson.

ColorStateWhat it says about the cell
GreensuccessThe task finished and its command exited cleanly
RedfailedThe task ran and its command exited with an error
Orangeupstream_failedA task upstream of it failed, so it will not run in this run
LimerunningA worker is executing the command now
GreyqueuedIts upstream tasks are done; it waits for a worker to pick it up
TanscheduledThe run has reached it and it is about to be queued
No colornoneThe run has not reached this task; an upstream task is still unfinished

The bar over a column carries the run's own state: lime while any task is still running, green once every task has finished with success, red once a task at the end of the graph has failed. The bar's height is the run's duration relative to its neighbours, so a night that ran long stands out as a taller bar before you read a single cell.

from datetime import datetime
from airflow.sdk import DAG, task
with DAG(
    dag_id="bookings_report",
    start_date=datetime(2026, 3, 1),
    schedule="@daily",
):
    @task.bash
    def get_bookings():
        return "./bookings.sh"
    @task.bash
    def get_reviews():
        return "./reviews.sh"
    @task.bash
    def build_report():
        return "./report.py"
    report = build_report()
    get_bookings() >> report
    get_reviews() >> report
  • Exports side by side
  • Exports chained
3 nights of bookings_report, March 2 to 4; the dependencies switch replays them with the exports chained instead of side by side.

Play the night above. At 00:00 on March 2 the first column appears and get_bookings and get_reviews turn lime together. build_report stays blank: the cell has no state until both tasks above it finish. At 00:10 get_reviews turns green and build_report still waits, because one of its 2 edges is unmet. At 00:20 the second edge is met, build_report passes through scheduled and queued and runs, and the column is all green at 00:35. On the night of March 3 build_report turns red at 00:35 and the bar over that column turns red with it: a run fails when a task at the end of its graph fails. Switch the dependencies to the serial file and get_reviews stays blank until get_bookings finishes at 00:20, then runs from 00:20 to 00:30; build_report starts at 00:30 instead of 00:20, so every column finishes 10 minutes later, at 00:45.

Click a cell and a panel opens with that task's attempt in that run. It shows the attempt's state and its start and end times with the duration between them, and its Logs tab holds what the command printed. Use the grid to find the cell, the log to see what the command did, and the graph to see which other cells could have been affected by the one you are looking at.

Reading a grid like a story

A grid is a record you read by shape. One red cell in an otherwise green row names a night that went wrong, and the log behind that cell is where to look. A whole row of red names a task that has been broken since a certain column, and the DAG file or command changed around that date is the suspect. A red cell with orange cells below it in the same column names a night where an early task failed and every task after it was marked upstream_failed; the red cell is the one to open, since the orange cells never ran. A column whose bar is lime right now is a run in progress, and the blank cells in it are tasks whose turn has not come.

Pattern in the gridWhat happenedWhere to look first
1 red cell, rest of its row greenOne night's command failedThat cell's log
Red across a row from one column onwardThe task broke on that date and stayed brokenThe DAG file or the command, as changed around that date
Red cell, orange cells below it in the columnAn early task failed and nothing downstream ranThe red cell's log
2 rows lime at the same timeThose tasks run side by side; no edge joins themThe graph view, to confirm the shape
Column bar lime, bottom cells blankThe run is still in progressWait, or watch the running cell

About this lesson

This section is part of the DAG Anatomy: Beginner lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.