Data Engineering for AI Systems from First PrinciplesPost 10 of 21 · 21 of 21 written
- How to use this guide
- 01Why AI runs on data engineering
- 02Representing data: bytes, types, schemas, formats
- 03Storage: where bytes live and what reading them costs
- 04Databases from first principles
- 05Data modeling: shaping data for its readers
- 06Distributed systems: the physics of many machines
- 07Batch processing: transforming data in bulk
- 08Streaming: data that never ends
- 09Architectures: warehouse, lake, lakehouse
- 10Pipelines and orchestration
- 11Data quality, contracts, lineage and observability
- 12Data engineering for machine learning
- 13Data engineering for LLMs
- 14Data engineering for RAG and AI applications
- 15Feeding GPUs: data loading for training
- 16Governance, privacy and security
- 17Cost and performance reasoning
- 18The tool landscape, mapped to principles
- 19Learning path and hands-on projects
- 20Question bank for fluency
- 21Glossary
10. Pipelines and orchestration
A pipeline is a graph of idempotent steps over partitioned data. Orchestration is the system that runs that graph in dependency order, on a schedule or on events, retries what fails, and lets you safely re-run history.
A pipeline step as a function
Think of every step as a pure function: output = f(inputs, code version, parameters). The parameter is usually a partition, such as “the data for 2026-10-29”. If a step really behaves this way, it is reproducible, testable and safe to retry. Most pipeline pain comes from steps that secretly depend on something else: the current time, a mutable table, a random seed, or an API that returns different answers.
DAGs
Pipelines are modeled as directed acyclic graphs: nodes are steps or datasets, edges are dependencies, and no cycles are allowed, so a valid execution order always exists.
There are two ways to think about the nodes:
- Task-centric (classic Airflow): nodes are actions. “Run this script, then that one.”
- Asset-centric (Dagster assets, dbt models): nodes are the datasets that should exist. “This table is built from those two tables.”
Asset-centric thinking maps directly to lineage and freshness questions: is this table up to date, and what feeds it? That makes it a better default for data and ML work.
Scheduling and data intervals
- Time-based: run every day at 02:00 (cron).
- Event- or data-driven: run when an upstream partition lands, a file appears, or a dataset updates.
Most orchestrators distinguish the run time from the data interval it covers. The run that starts on 30 October at 02:00 processes the data for 29 October. Confusing the two produces off-by-one-day bugs.
Full refresh versus incremental
| Approach | How | Pros | Cons |
|---|---|---|---|
| Full refresh | Recompute everything each run | Simple and always correct | Cost grows with history |
| Append by partition | Process only the new day or hour, overwrite that partition | Cheap and idempotent | Needs data partitioned by time |
| Merge by key with a high-water mark | Fetch rows with updated_at after the last run, upsert them | Handles updates | Misses deletes; clock and timing edge cases |
| CDC | Consume the source’s change log | Captures inserts, updates and deletes | More infrastructure |
Late-arriving data breaks naive incremental logic: an event for 28 October arrives on 30 October. The common fix is a lookback window: each run also reprocesses the previous few partitions.
Backfills
A backfill re-runs a pipeline over historical partitions, after fixing a bug or adding a new column or feature. Backfills are only safe if steps are idempotent and parameterized by partition. Plan their cost and parallelism: backfilling two years of daily partitions is 730 runs, and it can overwhelm a source database or a budget.
Ingestion patterns
- APIs: handle pagination, rate limits, retries with exponential backoff, and incremental cursors.
- Files: other parties drop files into object storage or SFTP. Validate them before trusting them.
- Databases: periodic snapshots for small tables, CDC for large or fast-changing ones.
- Event streams: consume from Kafka or similar into bronze tables.
Tools include Fivetran and Airbyte (many prebuilt connectors), dlt (a Python library for writing your own) and Meltano. For a single unusual source, a small custom Python job is often simplest.
Transformation with dbt
dbt turns SQL SELECT statements into managed tables. Each model is one SELECT; ref() declares dependencies, from which dbt builds the DAG. It adds tests, documentation and lineage, and runs the same code against dev and prod environments.
An incremental model that processes only new events:
-- models/silver/llm_calls.sql
{{ config(materialized='incremental', unique_key='request_id') }}
SELECT
request_id,
session_id,
CAST(ts AS TIMESTAMP) AS called_at_utc,
model_version,
prompt_version,
input_tokens,
output_tokens,
latency_ms
FROM {{ ref('bronze_llm_calls') }}
{% if is_incremental() %}
WHERE ts > (SELECT max(called_at_utc) - INTERVAL 3 DAY FROM {{ this }}) -- lookback for late data
{% endif %}The unique_key makes the step idempotent: reprocessed rows overwrite themselves rather than duplicating.
Orchestrators
| Tool | Model | Notes |
|---|---|---|
| Apache Airflow | Task-centric DAGs in Python | The most widely deployed; managed versions on every cloud |
| Dagster | Asset-centric, typed | Strong local development, lineage and partitions built in |
| Prefect | Pythonic flows | Light to adopt for Python-heavy teams |
| Kubeflow Pipelines / Argo Workflows | Containers on Kubernetes | Common for ML training pipelines |
| Flyte | Typed, versioned tasks on Kubernetes | Built for reproducible ML workflows |
| Temporal | Durable execution of long-running code | Workflows that must survive crashes, including AI agents |
The tool matters less than the discipline. Any orchestrator works if steps are idempotent, partitioned and observable.
Write-audit-publish
A pattern every serious pipeline should use:
- Write new output to a staging location: a branch, a temporary table or a staging partition.
- Audit it: row counts, schema, null rates, uniqueness, distribution checks.
- Publish it atomically only if the audit passes, swapping it into the place consumers read.
Consumers never see bad data, and a failed audit leaves yesterday’s good data in place. Iceberg branches and Nessie make this a first-class operation.
Pipelines are software
- Keep code, SQL and configuration in version control, and review changes.
- Run tests in CI against small sample data.
- Separate dev, staging and prod environments.
- Describe infrastructure as code (Terraform, Pulumi), and package jobs in containers.
- Keep secrets in a secrets manager, never in code or notebooks.
ML pipelines are the same thing
A training pipeline (prepare data, train, evaluate, register the model, deploy) is a DAG with the same rules. The difference is its artifacts: datasets, models and metrics, which must be versioned and linked to each other. Experiment trackers and model registries such as MLflow record those links.
Questions to ask
- Is each step a pure function of its inputs, code and partition?
- What exactly happens if this run is triggered twice?
- How do we handle data that arrives late?
- How long, and how much money, would a full backfill take?
- Can bad output ever reach consumers, or is there an audit gate?