ML Systems Lab Open interactive version →
Intermediate 22 min read RecSysfeature storetrain-serve skewfreshnesspoint-in-time

Features & Freshness

Real-time vs batch features, train/serve skew, the feature-freshness trap

A recommender's features come in two speeds, and the boundary between them is where most silent production bugs live. Get the freshness model wrong and your offline metrics look great while the live system quietly recommends yesterday's world.


Batch features are computed on a schedule; real-time features are computed per event. "User's 90-day purchase count" is a batch feature — recomputed nightly, cheap, stable. "Items viewed in the last 5 minutes" is a real-time feature — it must reflect *this* session, so it's computed online from a streaming store. The two live in different systems (a warehouse vs a low-latency online store), and the recommender reads both at serving time. The design question is which signals *need* to be fresh: session intent decays in minutes, so a stale "recent views" feature is nearly useless; long-term taste is stable, so a day-old "favorite genre" is fine.


Train/serve skew is the trap: the same feature computed two different ways. Training features are usually computed in a batch pipeline (Spark/SQL over historical logs); serving features are computed in a live path (streaming/online store). If the two implementations differ — a different null default, a timezone, a window boundary, a slightly different aggregation — the model trains on one distribution and serves on another. The model's live behavior degrades in a way *no offline metric can catch*, because offline evaluation uses the training-side computation. The fix is a feature store that guarantees one definition consumed identically at train and serve time.


The feature-freshness trap is skew's time-shifted cousin: point-in-time correctness. When you build training data, each label (did the user click at time t?) must be joined to feature values *as they were at time t* — not their current values. Join "user's total purchases" as of *today* onto a click from three months ago and you've leaked the future: the model learns from information it won't have at serving time, inflating offline metrics and collapsing in production. Point-in-time-correct joins (as-of joins on event timestamps) are the non-negotiable discipline that makes offline training data match serving reality.

Key points

Takeaway

RecSys features split into batch (stable, scheduled) and real-time (session, streamed), and the two hardest bugs both hide behind good offline metrics: train/serve skew (one feature computed two ways → distribution shift no offline metric catches) and non-point-in-time joins (current features glued onto historical labels → future leakage). A feature store with as-of joins is the discipline that closes both.

Recap

Check your understanding

Q1. Your recommender scores well offline but underperforms live. You discover the "items viewed in last hour" feature is computed with Spark (rounding timestamps to the hour) in training and with a streaming store (exact rolling window) at serving. What is this, and why did offline metrics miss it?

Q2. When constructing training data, you join each historical click to the user's *current* lifetime-purchase count. Offline AUC jumps to 0.98; production is far worse. What went wrong?

Q3. A teammate proposes making *every* feature real-time "to be safe." Select the *two* correct reasons this is the wrong default.

Try it interactively

ML Systems Lab is a free interview-prep platform for ML engineers — work through the full interactive module, quizzes, and drills.

Open ML Systems Lab →