Skip to work
All work
No. 152026MLOps · Point-in-time correctness

Feature StoreThe Leak You Can Measure

A minimal but correct feature store with point-in-time joins offline and a SQLite online store, built to make the leakage it prevents measurable rather than asserted.

Headline results

Naive join
1.00 offline → 0.61 online
Train/serve skew
0.39
Correct join
0.74 = 0.74
Method
merge_asof, point-in-time

System architecture

System architecture diagram for feature-store
Fig. 1 — Feature Store — system architectureFull size

Problem

A training row joined against the latest value of a feature has seen the future. The model scores beautifully offline and then underperforms in production, and because both numbers are real nobody can tell which one is lying. Feature stores are usually sold as infrastructure; the reason they exist is this one failure, and almost nobody measures it.

Approach

Two paths over the same event data. The offline path builds training rows with a point-in-time-correct as-of join, a backward merge_asof keyed on the label timestamp, so a feature event dated after the label can never enter the row. The online path materialises the latest value per entity into SQLite and serves it. A churn dataset is constructed so the trap is explicit: the feature carries a weak genuine pre-label signal and a strong post-label one, so a naive join looks great and a correct one does not.

Impact

The naive latest-value join scores a perfect 1.00 offline and 0.61 in production, which is worse than the honest model, for 0.39 of train-serve skew. The point-in-time join scores 0.74 offline and 0.74 online. That table is the entire argument for a feature store in two rows: the offline number you celebrate should be the online number you ship.

Decisions & tradeoffs

Engineer the dataset so the trap is unavoidable

The feature carries a weak real signal before the label and a strong one after it. That makes the leaky join score perfectly and the correct join score modestly, which is precisely the situation where a team ships the wrong one.

Keep the wrong join in the API

get_historical_features takes a leaky flag. Deleting the broken path would make the repo tidier and remove the only thing that proves the correct path matters.

Stay minimal on purpose

No registry service, no orchestration, no vendor. Point-in-time correctness and an online store are the two ideas that carry the value, and wrapping them in infrastructure would bury the measurement this project exists to show.

Build spec

Offline join
pandas merge_asof, backward, point-in-time correct
Online store
SQLite, latest materialised value per entity
Naive join
1.00 offline → 0.61 online (0.39 skew)
Correct join
0.74 offline = 0.74 online
Run
One command, self-contained

System notes

  • merge_asof with backward direction on the label timestamp, so no feature event from the future can enter a training row
  • The leaky join is kept as a first-class option, because the failure is the demonstration
  • A perfect offline score is presented as the warning sign it is, not the result
  • Online serving reads from materialised SQLite, so the two paths are genuinely separate code paths

Stack

pandas · merge_asof · SQLite · scikit-learn · Train/serve skew

View source on GitHub
Next project
Experimentation Engine · From Design To Decision