5.3.5 · D4MLOps & Deployment

Exercises — Feature stores

2,531 words12 min readBack to topic

Prerequisite links: Training-serving skew · Data leakage · Batch vs streaming pipelines · Redis · DynamoDB · Data versioning · Feature engineering · Model deployment


L1 — Recognition

Recall Solution L1.1

Online store. One row, right now, latency-critical → a low-latency key-value store such as Redis or DynamoDB. Why: the offline store (Parquet on S3, BigQuery) is built for scanning millions of rows cheaply, not for a single sub-10ms lookup.

Recall Solution L1.2

(b) Training-serving skew. Two code paths ( and ) produce different values from the same raw input. Detectable offline? No — offline metrics only see . The model looks fine until it meets in production.

Recall Solution L1.3

"Among all feature observations recorded at or before the label time , pick the most recent one." The part forbids the future; the part picks the freshest survivor.


L2 — Application

Recall Solution L2.1

Step 1 (filter, ): keep 08:00 and 09:15. Drop 11:40 — it is in the future of the event (using $500 would be Data leakage). Step 2 (): of {08:00, 09:15}, the largest is 09:15. Answer: $50.

Recall Solution L2.2

Allowed window: , i.e. . Check each survivor from L2.1: 09:15 is min old ; 08:00 is min old. Neither is inside the window. Answer: null / default. The model must explicitly handle "no fresh feature," rather than trusting a stale $50.

Recall Solution L2.3

The filter is — the is inclusive, so is allowed (it was known at the instant of the event). The 12:00 value is rejected. With two equal timestamps at 10:00, ties are broken by an ingestion order / row id chosen by the feature store; both are equally "freshest," so implementations pick a deterministic one (e.g. last-written). Answer: one of the 10:00 values (not 12:00).


L3 — Analysis

Recall Solution L3.1

Apply filter-then- per event. See the step-function figure below — the served value is a staircase that only ever steps at an observation time and holds flat until the next one.

Figure — Feature stores
  • : allowed = {09:00}. Answer $100.
  • : allowed = {09:00, 10:30}. Freshest = 10:30. Answer $40.
  • : allowed = {09:00, 10:30, 12:00}. Freshest = 12:00. Answer $90.
Recall Solution L3.2

Window per event: .

  • : window → 09:00 valid → $100 (unchanged).
  • : window → 10:30 valid (30 min old) → $40 (unchanged).
  • : window → 12:00 is 60 min old, valid → $90 (unchanged).

Trick answer: none change at . Now push TTL to :

  • : 12:00 is 60 min old null. This is the one that flips.
Recall Solution L3.3
  1. Access pattern mismatch. Training scans millions of rows of history (throughput-bound); serving needs one key, <10 ms (latency-bound). No single engine is best at both — see Batch vs streaming pipelines.
  2. Cost / storage shape. Offline keeps full history cheaply (columnar Parquet/BigQuery); online keeps only the latest value per entity in memory-fast KV (Redis/DynamoDB). Storing full history in Redis is wasteful; scanning Parquet per request is too slow. What syncs them: materialization — the job that copies computed feature values from offline into online, so both derive from one definition.

L4 — Synthesis

Recall Solution L4.1

Zero skew A feature store enforces this by registering a single feature definition and either (a) running that one definition in both paths, or (b) materializing the offline result straight into the online store so serving is a pure lookup, not a recomputation. Equality then holds structurally — you never rely on two hand-written implementations happening to agree.

Recall Solution L4.2
  1. Define the entity user with join key user_id.
  2. Register each feature (e.g. 7d_login_count) inside a feature view with a data source and TTL — one definition, so Feature engineering logic isn't duplicated.
  3. Build the training set with a point-in-time join: for each churn label at , attach features with (freshest). This blocks future leakage.
  4. Materialize the same definitions' latest values into the online store.
  5. Bundle the model's features into a feature service; at inference, look them up — no recompute → offline path online path, so skew . Ties into Model deployment.
Recall Solution L4.3

In-memory KV store (Redis) for <5 ms at high QPS (DynamoDB also viable at scale). Because the streaming source updates continuously, a gap (upstream outage) can leave the last value stale — set a TTL so a value older than returns null instead of silently serving a frozen click rate.


L5 — Mastery

Recall Solution L5.1

Window: ; take freshest inside.

  • : window → only 00:00 (90 min old, ok). 20.
  • : window → 02:00 valid (120 min); 00:00 excluded. Freshest = 02:00. 35.
  • : window → 05:00 is 210 min old excluded; 02:00 older still. No survivor. null.
  • : window → 09:00 valid (, 0 min old, inclusive ). Freshest = 09:00. 15.
Recall Solution L5.2

With : allowed = {09:00, 10:30}. Freshest = 10:30 → $40. But 10:30 is after the event at 10:00 — that observation did not exist at prediction time. The correct value is $100 (the 09:00 one). Failure: future leakage / Data leakage. Widening the upper bound past always risks pulling in post-event observations. The upper bound must be exactly (), never .

Recall Solution L5.3

No — the 12:20 value hasn't been materialized (next batch run is 13:00), so serving returns the pre-12:20 value. This is materialization lag. Lever: switch that feature to a streaming materialization path (Batch vs streaming pipelines) so writes to the online store happen near-real-time. Trade-off: streaming adds infrastructure complexity and cost, and can reintroduce skew if the streaming transform differs from the offline one — so the streaming path must reuse the same registered definition.


Recall Self-test checklist

The filter uses which comparison, strict or inclusive? ::: Inclusive, — a value known at the event instant is allowed. After filtering, which observation do you keep? ::: The freshest, i.e. the one maximising (). What makes zero skew structural rather than tested? ::: One registered definition + materialization, so by construction. When does TTL return null? ::: When even the freshest allowed observation is older than (i.e. ).