Why architecture matters here
ML fails on operations, not on algorithms. The model that got 0.94 F1 in the notebook now serves at 0.72 because the feature store diverged from the training pipeline. The model that shipped is now hallucinating because label drift went unnoticed. The regulator asks for lineage and no one can produce it.
The architecture matters because every one of these failures is preventable with the right substrate. A feature store enforces training/serving parity. A model registry provides lineage. Monitoring catches drift. Governance produces audit trail.
With the pieces in place, the model is one small thing inside a large, reliable system.
Why an ML pipeline is not just a data pipeline
A data pipeline has one honest failure mode: it throws. An ML pipeline has that mode too, plus one no exception handler will ever see - every stage succeeds and the output is wrong. Training completes, evaluation passes, serving returns 200s at a p99 of 14 ms, and the model is simply worse than it was last month.
Three structural properties produce this. The pipeline emits two artifacts, not one: a dataset, and a learned function whose correctness is statistical rather than logical, so no unit test fails cleanly on it. It contains a feedback loop - the model's own predictions shape the traffic and the labels, so yesterday's deployment is an input to today's training data. And quality signal arrives late: a chargeback label lands 60 to 90 days after the prediction it grades, so the metric that would catch a bad promotion does not exist at promotion time. The stages below are conventional; the contracts between them are what make the thing survivable.
Ingestion and validation - the first gate
Most silent degradation is upstream data that changed shape without telling anyone. A mobile release stops populating device_locale, the column goes from 4% null to 61% null, and the model keeps scoring and keeps being wrong - nothing errors, because null is a legal value. The defence is a validation stage that runs before anything downstream may read, treating the schema as a contract rather than a description:
- Schema - column presence, dtype, enum domain. New columns warn; removed or retyped columns hard-fail.
- Volume - row count inside a band of the trailing 7-day median. A 40% drop is a broken upstream job, not a quiet Tuesday.
- Distribution - null rate, cardinality, and quantiles per numeric column against the last accepted partition.
- Referential - join keys resolve at the expected rate; 99.5% falling to 88% means a stale dimension table.
The policy per check matters more than the check. Quarantine and proceed on the last good partition is usually right for training data - a stale sample beats a poisoned model. Hard-fail is usually right for feature materialisation, where serving stale features to a live model is worse than an error you can see.
Dataset versioning - what exactly did this model see
Audit, postmortems, and anyone reproducing your number ask the same question: which rows trained this model? "The last 90 days of events" is not an answer, because that table has been rewritten by late-arriving records since.
What makes it answerable is an immutable, addressable dataset identity. Iceberg and Delta expose a snapshot ID per commit, so a run recording events@snapshot=8842119 can re-read the exact bytes months later; where the table format cannot help, a content hash over the sorted input manifest costs one pass and buys the same guarantee.
Lineage is the graph over those identities: raw snapshot -> validated partition -> feature materialisation -> training sample -> model version -> deployment. Record each edge in the stage that creates it rather than reconstructing it later with a crawler, because reconstruction is where lineage systems quietly lie. Make it queryable in both directions - given a model, which data; given a suspect feed, which live models are downstream. When a supplier admits a fortnight of corruption, that reverse query is the difference between rolling back four models and rolling back everything.
The architecture: every piece explained
The top strip is the data-to-model flow. Data lake holds raw and curated data (Iceberg or Delta commonly). Feature store exposes both offline (batch, historical) and online (low-latency) feature views; a shared definition ensures training and serving see the same values. Training runs distributed with experiment tracking; every run logs code, data snapshot, hyperparameters, and metrics. Model registry stores versioned model artifacts with metadata and approval workflow.
The middle row is the serving and observation loop. Serving exposes online (real-time) and batch (offline scoring) inference. Shadow / canary splits traffic to compare new vs current model without user impact. Monitoring tracks input drift, prediction distribution, latency, and business quality (when labels arrive). Feedback loop writes labels back to the data lake for the next training cycle.
The lower rows are the substrate. Governance holds lineage (which data → which model), approval workflow, and audit. Compute plane allocates GPU and CPU with quotas per team. Metadata + orchestration — MLflow, Kubeflow, Airflow — orchestrates DAGs and stores metadata so every model in production is traceable back to its raw data.
Feature computation and the skew problem
Training reads features from a batch store over history; serving computes them from a request payload and an online store in single-digit milliseconds. Two code paths, one definition - and every divergence between them is invisible offline and lethal online.
Skew has three flavours worth naming separately. Definition skew: the batch job counts over calendar days, the online path over a trailing 24-hour window. Time-travel skew, which is label leakage: the training join takes the feature value as of now instead of as of the prediction timestamp, so the model trains on information it will never have at inference. Freshness skew: definitions match, but the online store materialises hourly while training assumed per-event updates.
A feature store collapses the first by making one registered definition serve both paths and the second by making point-in-time joins the default - feature store architecture has the internals. At pipeline altitude there is a cheaper move needing no product: log the feature vector serving actually used next to the prediction, then recompute those rows through the batch path. The distribution of the difference is your skew monitor, and it catches what a shared definition does not.
Training as a pipeline stage - reproducibility in practice
Treated as a stage rather than a notebook, training is a pure function of a manifest. Anything that influences the weights and is not in the manifest is a reproducibility hole.
run:
code_version: git:4f2a91c # commit, not branch
env_digest: sha256:9c1e... # image digest, not :latest
dataset: events@snapshot=8842119
feature_view: txn_velocity_v3 # registered definition + version
sample: 2026-04-01..2026-06-30, point_in_time=true
seed: 1337
hyperparams: {depth: 8, lr: 0.05, rounds: 1200}
hardware: 4x a100-80g # affects reduction order
Bit-exact determinism is usually not worth its price: non-deterministic GPU reductions and non-associative float addition make two runs of one manifest differ in the last decimal, and deterministic kernels cost throughput. The honest target is metric-level reproducibility - a rerun lands inside a noise band you have measured, by running one manifest three times before trusting any comparison. A 0.3-point offline delta means nothing when seed variance is 0.5. Add periodic checkpoints to object storage and the stage survives preemption; the training pipeline covers the distributed mechanics.
Promotion gates - the registry as a checkpoint
A trained artifact is a candidate, not a model. The registry's value is not storage but the set of predicates that must hold before a version changes stage. Encode them as executable gates rather than a review checklist, so they cannot be skipped by someone in a hurry:
- Offline metric clears the incumbent on the frozen holdout by more than the measured noise band.
- No regression beyond tolerance on any protected slice - region, device class, customer tier. Aggregate lift routinely hides a segment getting worse.
- Prediction distribution sane: calibration error in bound, no collapse to a constant, positive rate near the incumbent's.
- Provenance complete: manifest present, snapshot resolvable, image digest pinned, upstream validation green for every partition in the sample.
- Operational shape passes: artifact loads in the serving runtime, latency and memory inside budget on target hardware.
Slice regression and calibration are the two that catch real incidents, and both are cheap. Stage transitions (candidate -> staging -> production -> archived), approvals, and audit trail belong to the model registry; evaluation design to evaluation frameworks and model calibration.
End-to-end flow
End-to-end: a fraud team registers a new feature — transaction velocity per user last 24h. The feature store materializes it in both offline (nightly batch to Iceberg) and online (streaming to Redis) modes with a shared definition. A training run reads offline features, trains a gradient-boosted model, and logs run metadata. The model artifact is registered as v42 with metrics. Approval workflow requires a data-science lead to sign off. Deployment routes 5% shadow traffic to v42; monitoring shows fraud recall +3% with acceptable latency. Canary promotes to 50%. Monitoring holds. Full promotion to 100%. Two weeks in, monitoring detects input drift on velocity; the feature engineer investigates; a retraining is triggered from the same DAG. Every step has lineage.
Offline evaluation buys a candidate, not confidence
Offline evaluation measures on a fixed dataset; production measures under a distribution the model itself perturbs. The gap is structural, not noise: a recommender scored on logged impressions only ever sees items the old policy chose to show, and a fraud model's offline recall rests on labels that exist because the old model flagged those transactions for review.
So the pipeline needs an online arm, in order. Shadow - mirror live requests to the candidate, discard its output, compare predictions and latency at zero user exposure. Canary - a small real slice watched on guardrails. Then ramp. Blue-green fits swaps where the versions cannot coexist (changed feature schema, different runtime) and buys instant rollback, but for models it is the weakest of the three because it gives no comparison period.
Two things here are model-specific rather than service-generic. Rollback is cheap in artifact terms and expensive in data terms - the reverted model's predictions are already in the feedback loop and will appear in the next training set. And the ramp schedule is bounded below by label latency: going to 100% in an hour on a model whose quality metric materialises in six weeks is deploying blind. See shadow deployment, champion/challenger, ML A/B testing, and model serving.