Engineering Blog
Recommendations Nadia Petrov

How Stale Features Kill Recommendation CTR

A recommendation model trained on fresh data can fly blind when served stale features, especially session-level features batch pipelines cannot supply.

How Feature Staleness Kills Recommendation CTR

Recommendation systems are optimized for a single outcome: users clicking, watching, buying, or engaging with items the model surfaces. CTR is not the only metric, but it is the most direct signal that the ranked list reflects what users actually want right now. When CTR drops, the usual suspects are model staleness, catalog coverage issues, or diversity problems. Feature staleness rarely appears on the first-pass hypothesis list, which is part of what makes it so costly when it is the actual cause.

Feature staleness in recommendation systems is structurally different from the kind of staleness that affects fraud or anomaly detection. Fraud models need sub-second freshness to catch velocity patterns before they exceed a threshold. Recommendation models need session-level freshness: the ability to reflect what a user has expressed interest in during the current browsing or viewing session. That timescale is measured in minutes, not milliseconds. But batch pipelines, which run on hourly or nightly schedules, miss that window entirely.

Which features go stale first

Not all recommendation features degrade equally. Price and availability features are typically refreshed frequently through separate pipelines, so staleness there is uncommon. Static preference features, the long-term interest signals built from months of behavioral history, change slowly enough that an hourly batch update is adequate. The class of features that matters most and degrades fastest is session-level behavioral signals.

Session features include things like: category views in the current session, item interactions weighted by recency within the session, scroll depth on specific product types, and co-occurrence signals between items viewed in close temporal proximity. These features have a short useful life. If a user has spent twenty minutes looking at kitchen appliances this afternoon, the most predictive recommendation signal available is that session context. A feature computed from last night's batch does not contain that information at all. The feature value the model reads at inference time reflects yesterday's session patterns, not today's.

The resulting recommendation is not obviously wrong in the way that a broken model is obviously wrong. It simply reflects a user who does not exist. The model is ranking items for the user's historical self, not the user standing in front of the page right now.

Item-level recency and popularity signals also age quickly. Items trending right now, items purchased in the last hour, and items currently out of stock are cross-entity signals that change continuously and shape recommendations in time-sensitive settings such as flash sales or live events.

Measuring the gap in practice

We have been tracking feature age at inference time across a small set of early-access deployments and the pattern is consistent: for session-level features, the median feature age at serving time with batch pipelines is between four and ten hours. The model is making decisions about a user's current intent using signals that describe what that user was doing somewhere between mid-morning and yesterday.

The way this shows up in CTR metrics is subtle. You do not see a sudden cliff. You see a gradual degradation that looks like normal variance in A/B testing. The control group (batch features) achieves a baseline CTR, and a recommendation that used truly fresh session features would outperform it, but since both arms of the test typically use the same batch pipeline, the gap is invisible. You only see the opportunity cost when you introduce a treatment that actually uses current session state.

In our internal testing using a synthetic recommendation workload with simulated session data, pipelines with incremental session features updated per-event show meaningful CTR improvement over the same model fed hourly batch features. The exact numbers depend heavily on session structure, catalog size, and user behavior patterns, so we are careful not to claim a universal figure. The direction is consistent: fresher session features produce better recommendations.

The SQL that makes it work

The computation itself is not complicated. The challenge is running it continuously rather than periodically. A session affinity feature in SQL is a straightforward sliding window aggregate over recent events:

SQL
-- Session-level category affinity, updated on every event
CREATE MATERIALIZED VIEW session_affinity AS
SELECT
  user_id,
  category,
  COUNT(*) AS views_30m,
  SUM(dwell_seconds) AS dwell_30m,
  MAX(event_ts) AS last_view
FROM page_events
WHERE
  event_ts >= NOW() - INTERVAL '30 minutes'
  AND event_type IN ('view', 'click', 'add_to_cart')
GROUP BY user_id, category;

-- Recency-weighted item interaction score
CREATE MATERIALIZED VIEW item_recency_score AS
SELECT
  user_id,
  item_id,
  COUNT(*) AS interaction_count,
  MAX(event_ts) AS most_recent
FROM page_events
WHERE
  event_ts >= NOW() - INTERVAL '2 hours'
  AND event_type != 'impression'
GROUP BY user_id, item_id;

Feldera maintains both of these views continuously as events arrive from the Kafka topic. Each new page view updates the relevant user-category pair in session_affinity within the message delivery latency of the pipeline. The feature store reads the current values when the model requests features at inference time. There is no batch window and no staleness gap between event arrival and feature availability.

What changes at the serving layer

Serving fresh session features requires that the feature store query returns the current state, not a cached snapshot from the last batch write. If you are using a feature store that caches feature vectors and refreshes them on a batch schedule, the incremental pipeline alone is not enough: the cache invalidation must happen at the same cadence as the feature updates. Feldera writes updated feature rows to the output sink as they change, so any feature store that supports low-latency writes can stay current. Stores that only accept bulk batch loads remain a bottleneck at that layer.

The practical architecture is Kafka events arriving continuously, Feldera maintaining the session feature views and publishing row-level updates to the feature store's write API, and the model reading directly from the feature store at inference time. End-to-end latency from event to feature availability is typically under two seconds in this configuration, which is well within the session window that matters for recommendation quality.

Where freshness matters less

It is worth being direct about the cases where this investment does not move the needle. Long-horizon collaborative filtering, where the model learns from interaction history spanning weeks or months, is not sensitive to session-level feature freshness. The signal that drives those models is accumulated preference evidence, not current-session intent. Updating those features incrementally improves pipeline efficiency, but it does not meaningfully change model quality at inference time.

Similarly, content-based features that depend on item metadata (genre, price tier, release date) do not benefit from continuous updates unless the metadata itself changes frequently. The cases where you see direct CTR impact from incremental features are the ones where the model is supposed to reflect what the user is doing right now, not what they have historically done. Session behavioral features are the clearest example, and they are common enough in recommendation systems that this is usually the right first investment.

The recommendation case also illustrates a point about incremental computation more generally: the benefit is not uniform across all features in your feature library. Start with the features that are both highly predictive and highly time-sensitive, compute those incrementally, and leave less time-sensitive features on whatever pipeline they already have. The session features in a recommendation system are almost always in the first category.

Continue reading

Building a Fraud Score Pipeline with Sub-Second Feature Freshness Kafka to Feature Store: Bridging the Stream-Batch Gap When Is Your Feature Store Actually Fresh?

Try Feldera

Build your first incremental pipeline today

Feldera is free to start. Connect your data source, write a SQL view, and watch features update in milliseconds as events arrive.

Read the quickstart Create free account