MLOps / Pipelines and controlled delivery / Lesson 11
Training-serving skew and feature time
Compare the actual historical and delivered feature representations, including when each value became usable.
On this page
Lesson goals
- Distinguish feature event time, publication time, and age.
- Prevent retrospective corrections from entering historical examples improperly.
- Diagnose representation mismatch before attributing loss to drift.
Skew compares paths that should agree
Training-serving skew occurs when the representation used to fit or assess a model differs from the one delivered under its intended inference contract. Causes include separate transformation implementations, changed units, default values, category mappings, and feature freshness. Distribution shift is another concept: the population can change even when both paths implement the same function correctly. Diagnose whether the same raw observation and permitted context produce the same representation before treating a disappointing metric as evidence that the world changed. Keep raw meaning, transform identity, and output coordinates visible in a parity fixture.
Historical reconstruction has two time questions
A feature record has an event or effective time and a time when it was published or available to the prediction process. A backfilled correction can describe an earlier event while being unknown at that earlier prediction. The first fixture filters both time dimensions and a maximum age. An event-time-only lookup chooses a value that arrives too late. A feature-store product can implement particular timestamp and deduplication semantics, so verify its documented behavior and configuration rather than assuming that the phrase point-in-time automatically excludes every late correction. Availability metadata must itself represent the actual usable time faithfully.
history = [{"event":1, "available":1, "value":10},
{"event":3, "available":3, "value":30},
{"event":4, "available":7, "value":99}]
prediction_time, max_age = 5, 3
by_event = [r for r in history if prediction_time-max_age <= r["event"] <= prediction_time]
available = [r for r in by_event if r["available"] <= prediction_time]
correct = max(available, key=lambda r:r["event"])
naive = max(by_event, key=lambda r:r["event"])
print("available historical value", correct["value"])
print("event-time-only value", naive["value"])
print("feature age", prediction_time-correct["event"])
available historical value 30
event-time-only value 99
feature age 2
The later-event record was not available at prediction time; this explicit two-time policy also applies a declared maximum age.
Freshness is a task-specific validity condition
A maximum age can exclude a stale value, but a missing result then needs an explicit policy: reject, defer, use a defined fallback, or apply a documented missingness representation. Do not silently substitute the latest current value into an old example. Likewise, a recent event timestamp does not prove recent publication or successful materialization at the consumer. Preserve retrieval time, selected feature version, source status, and any fallback indicator when needed for diagnosis. A universal age limit is inappropriate because different measurements change at different rates and support different decision horizons.
Compare fitted state and implementation together
The second fixture uses the same raw number and mean but different scales, producing different model inputs. The mismatch could come from a stale package, an independently recomputed statistic, or configuration rather than the feature source. Compare names, units, types, category levels, missingness, transform version, fitted parameters, and selected historical values before final scores. A shared transformation definition can reduce duplication, but its dependencies and execution modes still need verification. Test boundary inputs and unsupported cases as well as a normal row, since two paths can agree on easy examples and diverge elsewhere.
def transform(value, mean, scale):
assert scale != 0
return (value-mean)/scale
raw_value = 14.0
training = transform(raw_value, mean=10.0, scale=2.0)
serving = transform(raw_value, mean=10.0, scale=4.0)
print("training representation", training)
print("serving representation", serving)
print("same", training == serving)
training representation 2.0
serving representation 1.0
same False
Identical raw input and feature name do not establish parity when stored transformation parameters differ.
Repair the earliest mismatched boundary
Locate whether the first discrepancy appears in source selection, temporal eligibility, normalization, encoding, batching, or model inference. Repair that boundary and retain a regression fixture that would fail under the original defect. Rebuild affected training or assessment artifacts if the mismatch invalidates prior evidence; changing only the current serving path does not repair a leaked historical evaluation. These programs perform fixed list selection and scalar transformation checks only. They do not query a feature store, materialize features, read a production stream, or validate a platformโs point-in-time implementation.
Common errors and fixes
Historical training uses a corrected value published later
- Why it happens
- Only event time constrained feature retrieval.
- How to fix it
- Apply the declared availability boundary as well as effective time.
- Prevent it next time
- Test a backfilled correction with distinct event and publication times.
A fresh-looking feature is unavailable to consumers
- Why it happens
- Event recency was mistaken for successful publication or materialization.
- How to fix it
- Track actual usable time and consumer retrieval evidence.
- Prevent it next time
- Include stale, delayed, missing, and fallback feature cases.
A raw-input match is treated as transform parity
- Why it happens
- Stored statistics, encoding, or defaults differ.
- How to fix it
- Compare full representations and fitted state before scores.
- Prevent it next time
- Keep known-input parity tests for both normal and boundary cases.
Check your understanding
1. Why must the value 99 be excluded at prediction time five?
Show worked solution
Its event time is four, but it is not available until seven. Under the declared usable-information policy it was unknown at five. The earlier value thirty is available and within the age window. Choosing 99 would reconstruct knowledge the predictor did not have.
2. The serving path is repaired after a historical leakage bug. Can the previous validation report remain authoritative?
Show worked solution
Not automatically. If the prior report used unavailable features or a mismatched representation, its evidence concerns the wrong procedure. Reconstruct the permitted historical inputs and reassess the candidate and baseline under the repaired contract, recording which earlier result is invalidated.
Key takeaways
- Effective time and usable time are distinct facts.
- Feature-store semantics must match the declared historical question.
- Repair representation mismatch and reassess any affected evidence.