When engineers ask whether their feature store is fresh, they usually check one of two things: when did the last pipeline run complete, or when was the last write to the feature table. Both are reasonable proxy metrics, but neither one answers the question that actually matters for model accuracy: how old is the information that went into the feature values?
There is a gap between these two definitions of freshness, and that gap is where the staleness your model experiences lives. Understanding this distinction is prerequisite to making meaningful improvements to feature pipeline latency.
Three Clocks in a Feature Pipeline
A feature pipeline involves at least three separate time references:
Event time. When did the underlying event occur in the real world? A transaction was approved at 14:23:07. A user clicked an item at 09:15:44. This is the ground truth time that the feature is supposed to reflect.
Ingestion time. When did the event land in the streaming or batch source that feeds the feature pipeline? Events frequently arrive with delay relative to event time, due to network latency, mobile device reconnection patterns, upstream processing queues, or log collection schedules. For a typical Kafka-based pipeline, ingestion lag is often under 10 seconds. For log-based batch imports, it can be hours.
Feature write time. When was the computed feature value written to the feature store, such that downstream model serving can read it? This is what most feature store dashboards show you when they report "last updated."
When you compute the freshness of a feature, you need to measure the full gap from event time to feature read time at serving. Measuring only from feature write time ignores the ingestion lag and any additional delay between when data was collected and when the pipeline processed it.
The Hidden Lag Stacks
Consider a simple pipeline: mobile app events flow through a message queue to a feature computation job, which writes to a feature store that is read by a model serving layer. At each stage there is a potential lag contribution:
- Mobile devices buffer events and batch them before sending, introducing up to 30-60 seconds of collection delay in some SDKs
- The message queue has a consumer group lag when the feature computation job cannot keep up with ingest rate
- The feature computation job runs on a schedule (hourly, 15-minute intervals), adding up to the full interval as batch wait time
- The feature store write may go through a buffer or cache layer with its own flush interval
- The model serving layer may cache feature lookups for a TTL window
Each of these is a small number that feels negligible in isolation. Together they compound. A 30-second collection delay, a 15-minute batch interval, a 60-second cache TTL, and an occasional 5-minute batch job runtime delay adds up to a worst-case feature age of nearly 21 minutes for events that arrive just before a batch window closes.
The number your feature store dashboard shows (the write time of the last successful pipeline run) tells you nothing about the 30-second mobile collection delay or the 60-second serving cache. It flatters your actual feature freshness by ignoring the edges.
How to Measure End-to-End Feature Lag
To get an accurate measurement, you need to track event timestamps end-to-end and compare them to the timestamps of the feature values that models consume at inference time.
The practical approach is to add an event_ts column to your feature store entries that records the event time of the most recent contributing event, not the pipeline write time. Then at inference time, log both the feature value and its event_ts. The difference between the inference timestamp and event_ts is the actual feature age.
-- Include event time provenance in feature output
CREATE MATERIALIZED VIEW user_activity_features AS
SELECT
user_id,
COUNT(*) AS events_1h,
SUM(value) AS value_sum_1h,
MAX(event_ts) AS most_recent_event_ts -- provenance timestamp
FROM user_events
WHERE event_ts >= NOW() - INTERVAL '1 hour'
GROUP BY user_id;
With most_recent_event_ts in the feature row, your model serving layer can compute the feature age at inference time as NOW() - most_recent_event_ts and log it alongside the prediction. Over time, this gives you a distribution of actual feature ages at inference rather than a best-case pipeline write timestamp.
Freshness Varies by Entity
One important nuance is that feature freshness is not uniform across entities. A high-activity user with hundreds of events per hour will have very fresh features because their aggregates are updated constantly. A low-activity user who had their last event three days ago will have a three-day-old feature, regardless of how often the pipeline runs.
This asymmetry matters for model calibration. If your model was trained on data where high-activity entities were overrepresented (common in many feature engineering workflows), the model may have implicitly learned to treat certain feature ranges as "recent" when they actually reflect stale low-activity entity behavior at serving time. The only way to catch this is to segment your feature age distribution by entity activity level and look for discrepancies.
Incremental computation does not eliminate this asymmetry; it just shifts the bound. With a continuously maintained view, a low-activity user's features will still reflect their last event, but that event is now reflected immediately when it arrives rather than being held in a batch window. The feature age for a given entity is bounded by when that entity last had activity, not by the batch interval.
The Write-Time Staleness Illusion
Feature store vendors typically report freshness as a pipeline health metric, using the write time of the most recent pipeline run. This is a reasonable operational metric for answering "is my pipeline broken?" but it is the wrong metric for answering "are my model's features up to date?"
A pipeline that ran 30 seconds ago and wrote features computed from data that was already 20 minutes old is not a fresh feature store. It is a fresh pipeline run serving stale data. The distinction matters because these two problems have completely different root causes and solutions. A stale pipeline run is a scheduling or infrastructure problem. Stale source data entering the pipeline is an ingestion architecture problem. They need to be measured separately and addressed separately.
The clearest way to expose this is to track two numbers for every feature set: pipeline write lag (the age of the last write) and data-to-write lag (the event-time age of the events that went into that write). The first tells you your infrastructure health. The second tells you your model health.
Where This Matters Less
Not all features need this level of scrutiny. Slowly-changing features like a customer's lifetime purchase history, 90-day category preferences, or account age are correctly computed from batch pipelines and their feature age is expected to be days. Measuring their staleness in seconds is noise. The freshness discussion is relevant for features whose signal value degrades within the model's decision window. For fraud and real-time recommendations, that window is measured in minutes. For credit risk scoring, it might be days. Calibrate your freshness requirements to the actual decision time horizon of the use case before investing in infrastructure to reduce lag.