You have collected raw data, cleaned tables, assigned labels, and created annotations. ETL (Extract, Transform, Load) wires those steps into repeatable pipelines: scheduled jobs, idempotent stages, and curated outputs that trainers and feature stores consume.
Notebook cells are prototypes; ETL is how AI engineering teams ship data daily without manual copy-paste. This lecture connects Module 4.1 topics to the orchestration mindset you will extend in later Volume 04 modules.
Learning Objectives
By the end of this lesson, students should be able to:
- Define extract, transform, and load stages in an ML data pipeline.
- Sketch a DAG from raw landing zone to training-ready Parquet.
- Compare batch ETL vs streaming ingestion trade-offs.
- Implement a small Python ETL script with logging and checkpoints.
- Apply idempotency and partition strategies for backfills.
- Hand off curated tables to Feature Engineering.
Introduction: Pipelines, Not One-Off Scripts
Extract pulls data from sources (APIs, DBs, files). Transform applies cleaning, joins, labeling merges, and business rules. Load writes curated datasets to the warehouse, object store, or feature store. In ML systems, transform often includes train/val/test splits and leakage checks from Data Leakage.
ETL transforms before load into the destination (classic data warehouse). ELT loads raw into the warehouse first, then transforms with SQL (common in Snowflake/BigQuery). ML teams use both: raw landing (ELT-friendly) plus Python transforms for complex NLP/vision logic.
Reference Architecture
| Zone | Contents | Mutability |
|---|---|---|
| Raw | API dumps, CSV snapshots, annotation exports | Append-only |
| Staging | Parsed, typed, deduped tables | Overwrite per batch ID |
| Curated | Joined features + labels, splits | Versioned (v1, v2) |
| Serving | Feature store, inference vectors | Point-in-time correct |
Batch vs Streaming
Batch ETL
- Hourly/daily cron, Airflow, Dagster, Prefect
- Full recompute or partition backfills
- Best for training sets and reporting
- Simpler correctness debugging
Streaming / Micro-batch
- Kafka, Flink, Spark Structured Streaming
- Low-latency features and online metrics
- Complexity: late events, watermarks
- Often paired with batch reconciliation
Most model training still uses batch curated tables; streaming feeds online features and monitoring. Volume 03 async I/O helps high-concurrency extract from APIs inside batch workers.
A Minimal Python ETL Job
"""ETL: raw events + labels -> curated training table"""
from pathlib import Path
import logging
import pandas as pd
logging.basicConfig(level=logging.INFO)
log = logging.getLogger("etl.train_table")
RAW_EVENTS = Path("data/raw/events")
RAW_LABELS = Path("data/labels")
CURATED = Path("data/curated")
CURATED.mkdir(parents=True, exist_ok=True)
def extract(batch_date: str) -> tuple[pd.DataFrame, pd.DataFrame]:
events = pd.read_parquet(RAW_EVENTS / f"dt={batch_date}")
labels = pd.read_csv(RAW_LABELS / f"labels_{batch_date}.csv")
log.info("extracted events=%s labels=%s", len(events), len(labels))
return events, labels
def transform(events: pd.DataFrame, labels: pd.DataFrame) -> pd.DataFrame:
events = events.drop_duplicates(subset=["event_id"], keep="last")
events["event_ts"] = pd.to_datetime(events["event_ts"], utc=True)
df = events.merge(labels[["event_id", "label", "guideline_version"]], on="event_id", how="inner")
df = df.loc[df["label"].notna()]
return df
def load(df: pd.DataFrame, batch_date: str) -> Path:
out = CURATED / f"train_table/dt={batch_date}" / "data.parquet"
out.parent.mkdir(parents=True, exist_ok=True)
df.to_parquet(out, index=False)
log.info("loaded rows=%s path=%s", len(df), out)
return out
def run(batch_date: str) -> Path:
events, labels = extract(batch_date)
curated = transform(events, labels)
return load(curated, batch_date)
if __name__ == "__main__":
run("2026-07-13")
Idempotency and Partitions
Re-running the same batch date should produce the same output path without duplicating rows in downstream consumers. Partition by dt=YYYY-MM-DD or batch_id so backfills replace one folder, not the entire dataset.
# Idempotent load — overwrite partition directory
partition = CURATED / "train_table" / "dt=2026-07-13"
if partition.exists():
for f in partition.glob("*.parquet"):
f.unlink()
df.to_parquet(partition / "data.parquet", index=False)
A DAG lists pipeline tasks and dependencies: extract_events → clean_events → merge_labels → publish_train_table. Orchestrators (Airflow, Dagster) schedule DAGs, retry failures, and alert on SLA misses.
Reality: ML engineers own transforms that affect labels, splits, and leakage. If you cannot rerun the pipeline, you cannot reproduce the model.
Reality: Training usually needs point-in-time batch snapshots; streaming complements with fresh features and monitoring.
Quality Gates in Load
| Check | Failure Signal | Action |
|---|---|---|
| Row count vs prior day | >50% drop | Block publish, page on-call |
| Label null rate | Spike after merge | Inspect label export version |
| Schema contract | Missing column | Fail CI on ETL script |
| Duplicate keys | event_id not unique | Fix transform dedupe |
Knowledge Check
- Short Answer: What are the three ETL stages? Answer: Extract, Transform, Load.
- True/False: Raw zones should typically be append-only. Answer: True.
- Multiple Choice: Daily training table rebuild is mostly: (a) streaming, (b) batch ETL, (c) manual Excel, (d) GPU compile. Answer: (b).
- Short Answer: Why partition by date? Answer: Enables idempotent backfills and efficient incremental reads.
- True/False: ELT loads raw before transforming in-warehouse. Answer: True.
- Multiple Choice: DAG stands for: (a) Data API Graph, (b) Directed Acyclic Graph, (c) Dynamic Auto GPU, (d) Duplicate Annotation Guide. Answer: (b).
- Short Answer: One quality gate before publishing curated data? Answer: Row count sanity check, schema validation, or duplicate key check.
- True/False: Transform step is where cleaning and label merges belong. Answer: True.
- Multiple Choice: After ETL, next Module 4.1 topic: (a) Feature Engineering, (b) HTML basics, (c) git init, (d) tuple unpacking. Answer: (a).
- Short Answer: What does idempotent batch rerun mean? Answer: Same input batch produces the same output without duplicate side effects.
Key Takeaways
- ETL turns Module 4.1 steps into scheduled, testable pipelines.
- Separate raw, staging, and curated zones with clear mutability rules.
- Batch jobs train models; streaming feeds latency-sensitive paths.
- Partition and idempotency make backfills safe.
- Next: Feature Engineering to build model inputs from curated tables.
Capstone wiring: Students chain collection → cleaning → label merge into one run(batch_date) script with logs and a row-count assertion.
Architecture whiteboard: Draw batch DAG vs streaming sidecar; discuss which steps must be identical at training and serving time.