Start With One Scored Request
A model can keep its offline validation score yet make different production decisions because a timestamp rounds differently, a categorical default changes, a join includes an event unavailable at scoring time, or serving uses a newer transform. A calibrated model is then answering a different question.
For a suspected incident, inspect one prediction end to end before aggregate dashboards. Capture entity ID, scoring timestamp, model version, feature-definition version, and the exact online feature vector. Rebuild the matching offline vector using only data available at that timestamp. This training-serving skew production diagnostic centers on that comparison, not a broad health review.
A distribution chart can look healthy while high-impact requests get the wrong bucket, default, unit, or freshness state. Prove entity-time parity first; use aggregate rates to size the incident later.
Parity Is a Time-Bound Contract
Parity is not matching offline and online columns. For the same entity at the same logical time, both paths must produce values equivalent under a feature-specific rule. The contract covers identity, event-time cutoff, transform version, and comparison rule.
Identity failures are subtle: training may join a canonical customer ID while serving receives an anonymous device ID resolved through a delayed mapping. Both return values but may describe different people. Time matters too: a seven-day purchase count needs an endpoint. count(events where event_time > score_ts - 7d and event_time <= score_ts) differs from a daily batch value made at midnight.
A name such as customer_age_days is insufficient when timezone, missing-value policy, rounding, or source priority can change without a version bump. Store the transform revision with sampled predictions; otherwise an investigation can mistake a legitimate migration for accidental divergence.
Use the initial metric:
Mismatch Rate(feature) = mismatched eligible comparisons / eligible comparisons
“Eligible” excludes records whose offline reconstruction cannot be made from retained history. Never count them as matches: they show the system cannot verify its claim.
Build the Offline-Online Diff
Create a sampled prediction ledger rather than retaining every raw feature forever. Each row needs request ID, entity key, score timestamp, model and transform versions, post-transform online values, and source-watermark metadata for freshness-dependent features. Reconstruct offline values from event history or a point-in-time feature store using the recorded score timestamp as cutoff. Normalize types, but never normalize away a semantic change such as dollars versus cents.
Comparison rules belong to features, not a platform-wide setting. A float tolerance suitable for a floating-point aggregate can hide an unacceptable error in a credit limit, price, or probability bucket.
| Tolerance class | Suitable features | Comparison rule | Failure interpretation |
|---|---|---|---|
| Exact | booleans, enums, IDs, hash outputs, missing-value flags | Value and null state match exactly | Code path, mapping, default, or version defect |
| Bounded numeric | amounts, normalized ratios, deterministic aggregates | Predeclare absolute and, where useful, relative tolerance | Units, rounding, precision, or aggregation cutoff |
| Time-derived | recency, age, rolling windows, source timestamps | Compare value and producing timestamp or watermark | Timezone, boundary inclusion, late events, or stale reads |
| Set or ranked | viewed items, candidate lists, top categories | Compare membership, material order, and cardinality | Filtering, deduplication, retrieval timing, or sorting |
Diff a stratified sample: high-volume traffic, recently deployed versions, device or region partitions with different request paths, and rows near time boundaries. Random sampling can miss the responsible branch. Retain matching and failing examples; their contrast often reveals the condition selecting the bad path.
For every mismatch, retain upstream timestamps and transform inputs, not only the final value. Zero can mean no events, failed join, missing source, default branch, or intentional eligibility rule. Telemetry must distinguish those states.
Schema Equality Can Hide Transform Errors
A small transform mismatch can evade schema review yet flip a model split. For days_since_login, offline calculates calendar-day distance while serving calculates elapsed full days.
# Offline training transform
feature = (snapshot_ts.date() - last_login_ts.date()).days
# Online serving transform
feature = int((score_ts - last_login_ts).total_seconds() / 86_400)
For login at 23:55 and scoring at 00:05 the next calendar day, training yields 1 and serving 0. Neither is universally wrong; they encode different business definitions. If a tree learned days_since_login <= 0, production takes another branch despite matching name, type, and apparent intent.
Use a shared fixture fixing last_login_ts, score_ts, timezone, and expected result. Run offline and serving transforms against it in continuous integration. Include midnight boundaries, applicable daylight-saving transitions, null timestamps, and events exactly at cutoff. The fixture is part of the feature contract; a wiki definition cannot prevent this regression.
Missing values create the same pattern: training may impute a median before scaling while serving maps a missing source response to zero. Both are numeric; divergence appears only when the diff records null state before final transformation.
Test the Clock, Not Only Values
Point-in-time leakage often appears as strong offline performance followed by unexplained online decay. Offline reconstruction may read only events at or before the prediction cutoff, subject to one explicit late-arrival rule. Warehouse load time is a valid availability proxy only when serving uses the same constraint.
For leakage, mutate an event after a fixed score_ts and rebuild the offline feature before and after; it must not change. If a future purchase changes a past purchase count, training used information unavailable at prediction. Repeat for labels and feature joins: future identity resolution can leak future knowledge into historical rows.
Freshness differs. A feature can have parity yet be stale because both paths use the same delayed snapshot. Inject or replay a known source update, then inspect source watermark, expected availability delay, and fallback behavior. A real-time balance older than its accepted delay breaches the contract; that age can be normal for a daily risk aggregate refreshed after batch close. Record intended cadence in the definition.
Boundary tests need ownership: decide whether an event exactly at score_ts belongs in the window and enforce it offline and online. Where clocks differ, define canonical event time and retain ingestion time for diagnosis. Silent timezone conversion can cause both leakage and apparent staleness.
Name the Incident Before Changing Code
A production decline does not prove skew. Four conditions can produce similar charts but require different fixes; use parity evidence and label maturity first.
| Condition | Direct evidence | What it is not | First diagnostic move |
|---|---|---|---|
| Training-serving skew | Entity-time diffs fail under the declared rule | Population shift alone | Isolate first divergent transform, source, or version |
| Data drift | Parity passes, but current feature or outcome populations differ from training | A two-pipeline mismatch | Segment by cohort, source, and feature semantics |
| Label delay | Recent outcomes are incomplete, censored, or maturing | Proof model quality fell | Re-score an older mature-label cohort with a fixed window |
| Model regression | Parity passes and mature labels worsen after a model change | A tracking issue until instrumentation is checked | Compare versions on the same eligible population and decision window |
This avoids changing a transform to fix delayed labels and introducing skew, or using drift monitoring to avoid a deterministic mismatch. Once parity is established, evaluate models with the controlled design in A/B testing ML models in production, rather than mixing model comparison with feature-contract root cause.
Alert Rules Must Follow Feature Semantics
Universal thresholds create noise for harmless numeric differences and weak alarms for material ones. A categorical eligibility flag needs exact-match alerting because one wrong value can route a request into another policy. A high-cardinality ranking may tolerate order changes beyond the visible cutoff, but should escalate when the top candidate changes for traffic where the model scores it.
Set thresholds by decision impact: start with tolerance class, then measure how often mismatches change a transformed bucket, score band, eligibility result, or downstream action. Mismatch rate without exposure can exaggerate a tiny sample; exposure without decision sensitivity can exaggerate a harmless difference. Triage combines affected traffic, feature semantics, and changed model behavior.
For current-data features, monitor score_ts - source_watermark against documented cadence. Avoid alerting on every late watermark, which trains responders to ignore alerts, and avoid average age alone, which hides stale subsets. Use percentiles and segment by source, region, or request path when they affect arrival.
Keep drift and parity monitoring separate. Drift asks whether the population changed; parity asks whether two representations of the same eligible data agree. A combined health score removes the clue needed for investigation.
Run a Bounded Diagnostic Checklist
During an incident, restrict the first pass to evidence that confirms or disproves parity. Dashboard tours produce hypotheses but rarely identify the broken contract.
- Select failed or suspicious predictions across affected segments; capture entity IDs, score timestamps, versions, online values, transform inputs, and source watermarks.
- Rebuild each vector point in time; classify fields as match, tolerated difference, mismatch, or unverifiable because history is absent.
- For the first material mismatch, reproduce the transform with a fixed fixture covering timezone, null state, cutoff boundaries, and upstream source response.
- Run future-event mutation and freshness tests before editing code; determine whether the condition is skew, drift, label delay, or regression.
- Repair the named contract, document its rule in the feature definition, and retain the failing fixture as a permanent regression test.
Do not declare success because aggregate predictions recovered: traffic mix, caching, or a temporary source change can explain recovery. The repaired feature must pass the entity-time reconstruction that exposed the defect.
A Parity Contract You Can Operate
Feature parity is a reproducible claim about a named entity, score time, transform version, and data visible at that instant.
With those anchors, repair is no longer guesswork. Fix the contract, decide how to handle affected predictions, and continue sampling the same comparison until it stays clean under ordinary traffic and time boundaries.