A feature store solves two problems: training and serving computing the same feature differently, and features that leak the future into training. The design follows from those.
A registry: each feature has a name, an owner, a definition (the transformation code), an entity (user, item), and a freshness expectation. This is what makes features discoverable and reusable across models.
An offline store: feature values with timestamps, in the warehouse or lake, used to build training sets. It must support point-in-time joins: for a training row with label time T, fetch the feature value as of T, never the latest. This is the leakage guard.
An online store: the latest value per entity in a low-latency key-value store (Redis, DynamoDB), for serving at inference time in single-digit milliseconds.
One definition, two materialisations: the same transformation code produces both stores, on a schedule for batch features and from a stream for real-time ones. Two hand-written implementations is exactly the skew the store exists to prevent.
# training set with point-in-time correctness
training = store.get_historical_features(
entity_df=labels[["user_id", "event_ts", "label"]],
features=["user:orders_30d", "user:avg_basket_90d", "item:ctr_7d"],
)
Operational concerns worth naming: backfills when a feature definition changes (recompute history, version the feature); monitoring for drift between offline and online values; and TTLs in the online store so a stale feature does not serve forever.
Say when not to build one: a single model with batch predictions does not need it. The store pays off at several models sharing features, or any real-time serving.
What they are checking: point-in-time correctness and the single-definition, dual-store idea.
Common mistake: describing a feature store as a database of features, with no answer for training and serving skew.