Lesson 12 Observability & SLAs
Theme:

Homework 12 — Observability & SLAs

Lesson 12 · Data Engineering with Python · 3 exercises

All three tasks build on the observability/ tree added in Lesson 12. Tasks 1 and 2 involve writing small Python modules and wiring them into the existing pipeline code. Task 3 is an observational exercise with a written reflection.

Task 1 — Add a volume SLA

Create data-engineering/sample-pipeline/observability/volume.py. This module should check whether the latest hour’s row count in the events table is at least 50% of the trailing 24-hour average. If it falls below that threshold, the pipeline has likely lost data silently.

Requirements:

Self-check: running on the sample data returns VOLUME OK (or VOLUME UNKNOWN if the table has fewer than 2 hours of data). Manually truncating the latest partition to 1 row and re-running returns VOLUME BREACH with the ratio printed.

cd data-engineering/sample-pipeline
python -m py_compile observability/volume.py && echo "syntax OK"
python -m observability.volume --sla-ratio 0.5 ; echo "exit=$?"

Task 2 — Wire the freshness alert into the DAG

Modify the _freshness_check function in data-engineering/sample-pipeline/airflow/dags/gharchive_hourly.py so that a BREACH calls alerts.alert("ERROR", "freshness", ...) before raising RuntimeError.

Requirements:

Self-check: run a forced breach from the terminal and confirm that a single JSON line with "severity": "ERROR" appears on stdout, followed by the RuntimeError:

cd data-engineering/sample-pipeline
python -c "
import sys
sys.path.insert(0, '.')
# Simulate a freshness check breach inline
from observability import freshness, alerts
rc = freshness.check(sla_minutes=0)
if rc == 1:
    alerts.alert('ERROR', 'freshness', 'events table SLA breach', db='warehouse.duckdb')
    raise RuntimeError('breach')
" 2>&1 | head -5

Task 3 — Read three runs in Marquez (or the JSON event files)

Trigger the gharchive DAG three times (or simulate three runs by running emit_start / emit_complete three times with different run_ids), then verify the results.

from observability.openlineage_emit import new_run_id, emit_start, emit_complete

for _ in range(3):
    rid = new_run_id()
    emit_start(rid, "gharchive_hourly.extract",
               inputs=["raw_events"], outputs=["dim_events"])
    emit_complete(rid, "gharchive_hourly.extract")

After running the code above, verify that exactly six event files were written (three START and three COMPLETE):

ls data-engineering/sample-pipeline/observability/events/ | wc -l

If you have Marquez running, open the Runs panel for gharchive_hourly.extract and confirm the three runs appear on the timeline.

Written reflection (one paragraph, no code required): what would a zero-run gap in the Marquez timeline tell you about your pipeline? Specifically: distinguish between (a) the pipeline tasks failing (which would show failed runs) and (b) the scheduler itself dying (which would show no runs at all). What does the absence of START events tell you that the presence of failed runs does not?

Self-check: six JSON files exist under observability/events/ (plus any from earlier steps); the written reflection addresses the gap-vs-failure distinction in at least two sentences.

View solutions →