◐ ai-teacher

🛡 Quality, Testing & Ops · Chapter 6

Observability: lineage, logs, metrics, sane alerts

Monitoring tells you something is wrong. Observability lets you ask why — without shipping new code first. At 3 AM, that difference is the whole night.

Study this lesson free in the app →

The basics

Data observability is being able to answer “what is happening in my pipelines and why?” from the signals they already emit — without adding print statements and rerunning.

Why it exists: a modern data platform has hundreds of jobs and thousands of tables. When the revenue dashboard is wrong, the cause might be six hops upstream in a job someone else owns. Monitoring (dq-004) tells you a number is off. Observability tells you which upstream table changed, which run produced it, what it logged, and who else is affected.

The mental model — four signals:

The first three are for investigating. The fourth is for interrupting — and should be rare.

Words to know: lineage graph, upstream / downstream, structured log, SLO, alert fatigue, runbook.

Explained

02:40. Forty-seven alerts arrive in four minutes: fct_rides stale, driver_payouts failed, city_dashboard stale, surge_features failed, finance_daily failed…

The on-call engineer opens them one by one. At 03:25 she finds the cause: one upstream source, app_events.ride_completed, stopped loading at 02:31.

With lineage, those 47 alerts would have been one incident with 46 downstream effects — and she'd have started at the root.

The hospital patient

A patient in the ICU isn't checked by a nurse asking “are you okay?” every hour.

A monitor that beeps for everything gets turned down by tired nurses. Then it misses the one that mattered. Alert design is a safety decision, not a technical detail.

A lineage graph — both directions matter

  app_events.ride_completed ──┐
                              ├─► stg_rides ─► fct_rides ─┬─► finance_daily
  gateway.payments ───────────┘                           ├─► driver_payouts
                                                          ├─► city_dashboard
  dim_drivers ────────────────────────────────────────────┘   └─► surge_features

  ◄──── walk UPSTREAM for root cause
        (finance_daily is wrong → what fed it?)

        walk DOWNSTREAM for blast radius ────►
        (I'm changing fct_rides.fare_inr → who breaks?)

Column-level lineage goes further: finance_daily.revenue comes from fct_rides.fare_inr, which comes from ride_completed.fare. Now a rename has a precise list of affected dashboards.

Logs and metrics a run should emit

import json, logging, time, uuid

log = logging.getLogger("pipeline")

def emit(event, **fields):
    log.info(json.dumps({"event": event, **fields}))

def run_fct_rides(partition):
    run_id, t0 = str(uuid.uuid4()), time.time()
    ctx = {"run_id": run_id, "job": "fct_rides", "partition": partition}
    emit("run_started", **ctx, inputs=["stg_rides", "dim_drivers"])

    rows_in = read_count("stg_rides", partition)
    good, rejected = transform(partition)
    write("fct_rides", good, partition)

    emit("run_finished", **ctx, status="success",
         rows_in=rows_in, rows_out=len(good), rows_rejected=len(rejected),
         duration_s=round(time.time() - t0, 1),
         outputs=["fct_rides"])

One line per event, JSON, with run_id everywhere. Now “show me every run of fctrides last week where rows_rejected > 0”_ is a query, not an afternoon of grep. The inputs / outputs fields are lineage for free — standards like OpenLineage formalise exactly this, and Airflow, dbt and Spark can emit it.

Metrics that catch problems early

Per job run:

Per dataset: freshness, volume, test pass rate.

Plot each as a time series. The incidents you prevent are the ones where a line was slowly bending for two weeks and somebody looked. Use p95 duration, not average — a job that's usually 10 minutes and sometimes 90 is a problem the average hides.

Is this alert worth a human?

Symptom-based alerting: page on what users experience (finance_daily not ready by 06:30), not on every internal cause (a retry happened). Causes go on dashboards for investigation.

The rename that almost shipped

Rohan opens a PR renaming fct_rides.fare_inr to fare_amount_inr — cleaner naming.

The CI step queries column lineage and comments on the PR:

fct_rides.fare_inr is read by: finance_daily.revenue, driver_payouts.base_earnings, surge_features.avg_fare_1h, and 3 Metabase dashboards (owner: finance-analytics).

Rohan keeps the old column as an alias for two weeks, announces the change in the data channel, and migrates consumers first. Lineage turned a Monday incident into a Thursday Slack message.

Gotchas

Words to know

ObservabilityAbility to explain a system's behaviour from the signals it emits, including questions you didn't anticipate.
LineageThe graph of data dependencies — which jobs read and write which tables or columns.
Impact analysisWalking lineage downstream to find everything affected by a change or failure.
Structured loggingLogs as machine-parseable events (usually JSON) with consistent fields like run_id and dataset.
OpenLineageAn open standard for emitting lineage events from tools like Airflow, dbt and Spark.
Symptom-based alertingAlerting on what consumers experience (late or wrong data) rather than on every internal cause.
RunbookA short, written procedure for responding to a specific alert.

In two minutes

Test yourself

1. Revenue on finance_daily is wrong. Which direction do you walk the lineage graph to find the cause?

  1. Downstream
  2. Upstream
  3. Neither — check the dashboard's SQL only
  4. Both at once
Show the answer

Upstream — Causes are upstream — whatever fed the table. Downstream tells you who else is affected, which matters for communication, not diagnosis.

2. You plan to drop a column from fct_rides. What does column-level lineage give you?

  1. Faster queries
  2. The column's null rate
  3. Automatic migration
  4. The exact list of downstream models and dashboards reading that column
Show the answer

The exact list of downstream models and dashboards reading that column — Impact analysis: you can notify or migrate exactly the affected consumers before breaking them.

3. Which log line is most useful at 3 AM?

  1. print('done')
  2. INFO: finished processing
  3. {"event":"run_finished","run_id":"a1f…","job":"fct_rides","partition":"2026-11-04","rows_in":95214,"rows_out":87310,"rows_rejected":7904}
  4. DEBUG output of every row
Show the answer

{"event":"run_finished","run_id":"a1f…","job":"fct_rides","partition":"2026-11-04","rows_in":95214,"rows_out":87310,"rows_rejected":7904} — Structured, filterable by run and partition, and it shows the anomaly directly — 8% of rows rejected. The others are unsearchable or drown the signal.

4. A task fails once, retries automatically, and succeeds. The table is on time. Should it page someone?

  1. No — the consumer-facing outcome is fine; record it on a dashboard
  2. Yes — every failure must page
  3. Yes, but only on weekends
  4. No — and stop logging retries
Show the answer

No — the consumer-facing outcome is fine; record it on a dashboard — Symptom-based alerting: no consumer felt it. Keep the retry visible in metrics so a rising retry rate can be spotted, but don't wake anyone.

5. fct_rides p95 duration has grown from 12 to 41 minutes over five weeks; it hasn't breached the SLA yet. What is this signal?

  1. Noise — ignore until it fails
  2. A reason to page on-call now
  3. An early warning worth a ticket — likely growing scan, skew or missing pruning
  4. A lineage problem
Show the answer

An early warning worth a ticket — likely growing scan, skew or missing pruning — Trends are what metrics are for. Fix it in daylight as a ticket rather than at 3 AM when it finally crosses the SLA.

6. Trap: one upstream source stops loading and 47 downstream alerts fire. What is the best improvement?

  1. Lower alert thresholds
  2. Mute all downstream tables permanently
  3. Add more alerts on each downstream table
  4. Group alerts by lineage root so the cascade is one incident at the source
Show the answer

Group alerts by lineage root so the cascade is one incident at the source — The downstream alerts are symptoms of one cause. Grouping keeps the information (what's affected) without the storm, and points the responder at the root.

There are 8 questions, 3 puzzles and 5 recall cards for this lesson in the app — the recall cards come back on later days, just before you would forget.

Learn it so it stays. Read it, test yourself, then recall it on two different days. The app schedules that for you, free.

Start this lesson →