Data & Analytics
The feature that quietly broke your model
Aman Mundra · July 4, 2026 · 9 min read

Contents
- 1. Two diseases, one symptom
- 2. Skew: the bug you shipped on day one
- 3. Drift: the world moving out from under you
- 4. Why the pipeline stays green while the model dies
- 5. Input-first monitoring: watch the features, not just the score
- 6. The parity test every serving path owes you
- 7. Feature stores help, and what they can't do
- 8. Triage: skew, drift, or model?
- 9. Takeaways
- References
A model that performed well for months starts making bad calls, and the first instinct is always the same: retrain it. Usually the model is innocent. Something upstream changed - a feature's units shifted, a join started dropping rows, a source system began sending nulls where it used to send values - and the model kept doing exactly what it was trained to do, with inputs that quietly stopped meaning what they used to. Nothing crashed. The pipeline stayed green. The predictions stayed fluent. Only the decisions got worse.
The scale of this problem is not anecdotal: Gartner reports 67 percent of enterprises see measurable model degradation within twelve months of deployment, and the highest-profile postmortems keep tracing it to inputs, not weights. This post is about the two distinct diseases that get lumped together as "drift," the reason your green dashboards miss both, and the input-first monitoring layer that catches the culprit before your users do.
Table of contents
1. Two diseases, one symptom
2. Skew: the bug you shipped on day one
3. Drift: the world moving out from under you
4. Why the pipeline stays green while the model dies
5. Input-first monitoring: watch the features, not just the score
6. The parity test every serving path owes you
7. Feature stores help, and what they can't do
8. Triage: skew, drift, or model?
9. Takeaways
1. Two diseases, one symptom
"The model degraded" describes two mechanically different failures, and the fix for one wastes months on the other:
- Training-serving skew is a bug: the model receives different data at serving time than it trained on, even though reality never changed. Different missing-value defaults in the training and serving code paths, a timezone conversion applied once instead of twice, a stale cache feeding the online path. Per JFrog ML's definition, skew is present from the first prediction - your offline metrics were never true.
- Drift is a statistical phenomenon: the production data distribution moves over time - new customer segments, seasonality, changed user behavior, an upstream product launch. Per Evidently's taxonomy, the pipeline is faithful; the world is not.
The distinction matters because the remedies are opposite. Skew is fixed with engineering (unify the code paths, fix the join); retraining on skewed features just launders the bug into the weights. Drift is fixed with retraining and adaptive processes; debugging the pipeline finds nothing because nothing is broken. Diagnosing which one you have is the whole game, and section 8 gives the decision procedure.
2. Skew: the bug you shipped on day one
The canonical war stories, because the numbers deserve to be famous:
- DoorDash, investigating why online performance lagged offline metrics, found their dual-pipeline setup (one path computing training features, a separate path serving online) had feature-value mismatches as high as 35.7 percent from staleness and cached residuals. A third of a feature's values differing between what the model learned and what it saw.
- Google Play compared statistics of serving logs against training data for the same day and found features always present in training that were always missing in serving. Removing that skew improved app-install rate on the main landing page by 2 percent - on one of the most heavily optimized surfaces in the industry, from deleting a bug rather than adding a model.
The root cause is almost always the same architecture smell: the same transformation implemented twice - once in the offline training pipeline (Spark, SQL) and again in the online service (Java, Go, Python). Two implementations of "normalize this string" or "default this null" diverge in small ways - tokenization, rounding, encoding order - and the divergence compounds across dozens of features. Every re-implemented transform is a skew generator with a delay timer.
3. Drift: the world moving out from under you
Drift comes in grades, and the grades matter for response:
- Covariate drift - input mix shifts (more mobile traffic, new geography, marketing changed the acquisition channel). The relationship between features and outcome may still hold; performance erodes at the edges first.
- Concept drift - the relationship itself changes (fraud patterns adapt, a competitor repriced, a pandemic rewrote demand curves). The model's learned mapping is now wrong even on familiar-looking inputs. Fraud and pricing models live under permanent adversarial concept drift.
- Label drift - the outcome base rate moves, silently invalidating calibrated thresholds even when ranking is intact.
The operational trap is that drift arrives gradually while your evaluation arrives in batches: by the time the quarterly accuracy review shows the drop, the model has been mispricing decisions for a quarter. And retraining without diagnosis has its own failure mode - a model retrained on a window that included weeks of corrupted upstream values carries the damage forward, which is why the skew-versus-drift question comes before the retrain button.
4. Why the pipeline stays green while the model dies
The monitoring you already have was built for services, and services fail loudly. Feature pipelines fail politely. As the pipeline-monitoring literature catalogs: the orchestrator reports success while the query returned zero rows; the schema change didn't crash the job, it just re-mapped field positions so the model reads a different column; the upstream team changed int to string, a cast succeeded, and a unit changed from dollars to cents. Nothing crashes - the model is simply learning from a reality that no longer exists.
This is the structural blind spot: orchestration alerts test that jobs ran, not that data means what it meant. Output-side monitoring (accuracy, business KPIs) eventually notices, but "eventually" is measured in user harm, and it tells you nothing about where to look. The missing layer sits between the two - on the inputs.
5. Input-first monitoring: watch the features, not just the score
The layer that catches both diseases early, in order of value per hour of setup:
# Per feature, per day (cheap, do first):
null_rate, distinct_count, min/max/mean/percentiles
alert on: z-score vs trailing 28d, new categories,
null-rate step changes, volume drops
# Distribution distance vs training snapshot (second):
PSI or KL per feature; alert PSI > 0.2 # rule of thumb
segment-level too - aggregate PSI hides segment breaks
# Skew-specific (the one nobody builds):
same-day comparison of TRAINING-PATH values vs
SERVING-LOG values for identical entities.
Google Play's method - it finds bugs the other
two layers mathematically cannot see.
# Output side (keep, but as the LAST line):
prediction distribution, calibration, business KPI
- plus delayed ground-truth accuracy when labels land
Two design notes. First, log the serving-time feature vector with every prediction - it is the single highest-value artifact in any ML incident, converting "what did the model see in March?" from archaeology into a query. Second, route feature alerts to the team that owns the pipeline, not the model - the fix is almost never in the weights, and misrouted alerts are how these incidents age from hours into weeks. Tooling exists at every budget: BigQuery ships skew/drift monitoring as SQL functions, Vertex and the open-source stack (Evidently et al.) cover the rest; the hard part is deciding to watch inputs at all.
6. The parity test every serving path owes you
Monitoring catches skew in production; the parity test prevents it from shipping. It is one assertion, and it belongs in CI:
# For N historical entities: offline = training_pipeline.compute_features(entities, as_of=T) online = serving_path.compute_features(entities, at=T) assert offline == online # exactly. not approximately. # Run: on every feature-code change, every deploy, # and nightly against fresh entities. # The first week this exists, expect it to fail. # That failure is the 35.7% you weren't measuring.
Teams resist this test because making it pass forces the real work: a single source of truth for each transformation, exercised by both paths. That work is the fix. Everything else in this post is detection; parity is prevention.
7. Feature stores help, and what they can't do
The standard mitigation is a feature store, and the standard claim is true as far as it goes: define each feature once, serve the same values offline and online, get point-in-time correctness against label leakage. If your training and serving paths are two codebases today, a feature store (or even a disciplined shared library) removes the biggest skew generator you have.
The honest caveat, argued sharply in "Why feature stores didn't fix training-serving skew": stores manage data artifacts, not execution. They cannot guarantee when a feature was computed, which version of the logic ran, or that a join behaved identically across contexts - every system boundary a feature crosses reintroduces probabilistic consistency. DoorDash's 35.7 percent happened around sophisticated infrastructure, via staleness and caching - exactly the class of problem a store does not dissolve. So: adopt the store for definition-level consistency, and keep the parity test and same-day skew comparison anyway. Infrastructure reduces the attack surface; only measurement closes it.
8. Triage: skew, drift, or model?
The decision procedure we use in incidents, in order:
- Offline great, online flat from day one? Skew. Run the parity test; diff same-day training-path vs serving-path histograms per feature. (If you never had a true baseline, suspect this first - skew is why "it worked in the notebook.")
- Was good, stepped down on a date? Pipeline event. Diff feature stats around the step; check upstream deploys and schema migrations that day. A step change is never statistics - something shipped.
- Was good, eroding gradually? Drift. PSI per feature and per segment identifies which inputs moved; concept drift shows as stable inputs with decaying calibration. Now - and only now - retraining is the remedy, on a window that excludes any corrupted period found above.
- Inputs clean, parity passes, distributions stable, still bad? Congratulations, it might actually be the model. In our experience this is the least common branch - which is the whole point of running the others first.
9. Takeaways
- "Model degradation" is two diseases: skew (a bug, present from day one) and drift (statistics, arriving over time). The fixes are opposite; diagnose before retraining.
- The famous numbers are input-side: DoorDash's 35.7 percent feature mismatch, Google Play's 2 percent install lift from deleting one skewed feature. The model is usually innocent.
- Green pipelines prove jobs ran, not that data means what it meant. Add the input layer: per-feature stats, PSI against training, and same-day training-vs-serving comparison.
- Log the serving-time feature vector with every prediction, and route feature alerts to pipeline owners.
- Put the offline/online parity assertion in CI. Expect it to fail the first week; that failure is your unmeasured skew.
- Feature stores fix definition-level skew and are worth adopting - but they don't control execution. Keep measuring.
References
- What is training-serving skew in machine learning? (JFrog ML)
- What is data drift in ML, and how to detect and handle it (Evidently AI)
- Training-serving skew, incl. the Google Play case (DS Wok)
- The AI drift problem: prevent silent model decay (V2 Solutions)
- Silent schema changes break more than tuning (Medium)
- Data pipeline monitoring: stopping silent failures (Anomaly Armor)
- Monitor ML model skew and drift in BigQuery (Google Cloud)
- Why feature stores didn't fix training-serving skew (DEV)
Hero image: Valentin Salja on Unsplash.
This input-first observability is what keeps models honest in production. Explore our other insights or get in touch if you would like to talk it through.










