After late trip uploads restate a driver's 30-day aggregate, why must the feature row be versioned rather than overwritten in place?
answer
- keep versions, never overwrite
- validity interval in knowledge time
- half-open, so one match per instant
- Type 2 slowly changing dimension
- reproducible rebuild a year later
basics
~20 sOverwriting destroys what the feature store held at earlier quote instants, so every training row assembled afterwards silently receives the restated value. Storing each value with a valid-from and valid-to interval lets an as-of join reproduce what a past quote actually saw.
solid answer
~50 sAn as-of join can only select a past value if the past value still exists. If the pipeline updates one row per driver in place, the moment a late upload restates a 30-day aggregate from 1.4 to 2.9 the earlier figure is gone, and a training set built next week gives a March quote the September number - a latest-value join wearing an as-of join's clothes. The fix is the **Type 2 slowly changing dimension** shape: never update, always append a new version carrying `valid_from` and `valid_to` in knowledge time, with the current version left open-ended. Keep the intervals half-open so exactly one version matches any instant. The payoff is reproducibility: the same query, run a year apart, returns identical training data, and any past pricing decision can be explained with the figures it was made on.
code
json · 20 lines[
{
"driver_id": "D-8821",
"feature": "harsh_braking_per_100km",
"value": 1.4,
"event_window_start": "2026-03-02",
"event_window_end": "2026-03-31",
"valid_from": "2026-04-01T02:10:00Z",
"valid_to": "2026-04-05T02:10:00Z"
},
{
"driver_id": "D-8821",
"feature": "harsh_braking_per_100km",
"value": 2.9,
"event_window_start": "2026-03-02",
"event_window_end": "2026-03-31",
"valid_from": "2026-04-05T02:10:00Z",
"valid_to": null
}
]go deeper
Remember that a table holding one value per driver can answer what the value is now and nothing about what it was. Assembling training rows needs the second answer.
Describe the version shape: append a row with a validity interval in knowledge time instead of updating, keep the intervals half-open, and select by containment on the row's own timestamp.
Show what versioning buys operationally - an identical training set on rebuild, a defensible account of a past price - and name the failure modes of closing an interval badly, such as gaps and overlaps.
Own the retention question: how far back history must reach for the rebuilds and audits the business actually performs, what that costs in storage, and who decides when a version may be thinned.
## Why one value per key is not enough The natural shape for a feature table is one row per entity: driver `D-8821` has a `harsh_braking_per_100km` of 2.9, updated whenever the pipeline recomputes. It serves request-time reads perfectly well, and it is useless for assembling training data, because it answers only one question - `what is the value now?` - while every training row asks a different one: `what was the value at this row's quote instant?` Restatement is what turns that from a theoretical objection into a defect. A 30-day window closes, the pipeline computes 1.4, a quote is priced against it, and four days later a buffered trip lands and the same window recomputes to 2.9. Under in-place update, the 1.4 no longer exists anywhere, and there is no record that it ever did. ## The Type 2 shape Instead of updating, append. Each value becomes a version with its own interval of validity in **knowledge time**: - `valid_from` - the instant the store began serving this value; - `valid_to` - the instant a newer version superseded it, left open for the current version; - the **event window** the value summarises, kept as its own start and end. Two rules keep it honest. Use **half-open intervals** (`valid_from <= t < valid_to`), so exactly one version matches any instant and no timestamp matches two. And write a new version **only when the value actually changes**, so a driver who has stopped uploading stops generating rows. | | in-place update | versioned interval | |---|---|---| | answers `value now?` | yes | yes, the open version | | answers `value on 3 April?` | no | yes | | training set reproducible | no, drifts with every rebuild | yes, identical forever | | storage | one row per driver | one row per change | | read cost at request time | a single lookup | a lookup plus an interval filter | | explains a past decision | no | yes, with the figures used | ## Reading it back An as-of join now has a predicate that means something: for each quote row, take the version where `valid_from <= quoted_at` and `valid_to` is either null or strictly after `quoted_at`. A March quote reads 1.4 forever, whatever the pipeline learns later; a quote after the restatement reads 2.9. Two rows for the same driver, days apart, legitimately disagree - which is the point. ## What it costs 1. **Storage grows with change, not with time.** Active drivers in a volatile window generate versions daily; dormant drivers generate none. Sizing follows the change rate, not the entity count. 2. **Every read gets an extra predicate.** The offline assembly pays an interval filter it did not pay before, which is a real but modest scan cost. 3. **Closing a version correctly is fiddly.** The old version's `valid_to` and the new version's `valid_from` must be the same instant, in the same clock, written in the same transaction - otherwise a gap or an overlap appears, and the join silently returns nothing or two rows. 4. **Old versions eventually need a policy.** Nothing here says keep them forever; it says keep them at least as far back as the oldest training row you intend to rebuild, and a conscious retention decision has to exist. ## The failure it prevents Without versioning, two training sets assembled from the same code a month apart disagree, and neither is wrong by its own lights. A model cannot be rebuilt from its recorded inputs, an offline comparison between a champion and a candidate quietly uses different data, and no one can reconstruct the figures that justified a particular price to a driver who asks. ## The part that is easy to get backwards The restated value is not a corruption. Once the buffered trip lands, 2.9 is a better description of that driver's 30 days than 1.4 ever was - and it is still not the number the March quote was priced on. Both facts are true at once, and versioning is precisely the mechanism that lets the store hold them both instead of choosing.
- Why does a versioned feature row need timestamps beyond valid-from and valid-to?Those two bound knowledge time - when the store believed the value. They say nothing about which driving it summarises, and two versions of the same window are indistinguishable without the window's own start and end. An as-of join needs both pairs: the event-window bounds to keep post-quote driving out, and the validity interval to keep post-quote recomputations out.
- What stops the version history from growing without bound?Versions are written only when a value changes, and restatements concentrate in a short period after an event window closes, so most keys go quiet quickly. Beyond that it is a retention decision with a stated trade-off: history must reach back at least as far as the oldest training row anyone intends to rebuild, and older versions can be thinned once no assembly reaches them.
- A quote falls exactly on the instant a new version starts. Which version should it read?With half-open intervals the answer is unambiguous: the new one, because the predicate is `valid_from <= quoted_at` and `valid_to > quoted_at`. Closed-closed intervals make that instant match two versions, which either duplicates the training row or returns whichever the scan found first - a defect that appears only for the handful of rows landing on a boundary and is painful to track down.
saying these in an interview costs you the question
- Says a nightly full rebuild of the table preserves history.
- Keeps only the newest value and calls the column immutable.
- Uses closed-closed validity intervals, so one instant matches two versions.
- Argues the restated value is more accurate, so training should use it everywhere.
- Stores only ingestion time and infers the event window from it.