Feature Store Architecture
Offline store, online store, registry, materialisation, latency SLAs
Five ML teams at one company all need the same thing: "user average spend in last 30 days." Each team builds its own pipeline. Five nightly SQL jobs. Five Redis keys. Five different implementations of the 30-day window, each with its own null handling, timezone quirks, and new-user edge cases. A user spends ten thousand dollars at midnight, and each team's copy of the feature updates at a slightly different time with a slightly different number. The fraud model and the recommendation model now disagree about this user's spending. Neither team can see the other's value. Neither is wrong by its own logic. Yet they are quietly inconsistent, and both models suffer for it.
A feature store fixes this by computing each feature *once,* correctly, and serving that one answer to everyone — through two storage backends built for two very different jobs.
The offline store: history, for building training sets
It keeps every past value of a feature, stamped with time. That is what lets you ask, "What was this user's 30-day average spend *as of* time T?" — the point-in-time query that prevents leakage. Without it, your training pipeline reaches for *today's* values when building rows for last month's events, and every rolling aggregate silently swallows data that didn't exist yet when the prediction would have been made.
The online store: the current value, fast
A live fraud request needs this user's spend average in under 5ms. The online store (Redis, DynamoDB, Cassandra) holds only the *latest* value per user and returns it at memory speed. No history — that's the offline store's job. It's sized for latency at peak traffic, not for storage.
Online-store latency failure modes. The online store's P50 latency can look fine while P99 spikes badly, and the usual causes are specific, not vague "network blips": a hot key (one popular entity — a viral post, a high-volume account — absorbing a disproportionate share of reads, so requests queue up behind it), memory pressure that forces the store to evict cached keys under load, or an oversized serialized feature vector that's slow to deserialize on every read. The fixes track the causes directly: hash or shard hot keys so no single partition takes outsized traffic, add capacity so eviction pressure eases, and put a circuit breaker in front of the store so a P99 spike degrades gracefully instead of cascading into the caller.
The registry and materialization: the parts a plain database lacks
The *registry* is the governance layer: it records each feature's definition, owner, freshness SLA, upstream dependencies, and which models consume it — so teams can find what already exists and know what breaks if a pipeline is retired. *Materialization* is the act of computing features from raw data and writing them to both stores: batch (Spark, Airflow, hourly/daily) when some staleness is fine, or streaming (Kafka → Flink → Redis) when 5-minute freshness matters, like fraud or live inventory. Each write to the online store is typically stamped with a TTL a little longer than the materialization interval -- insurance against a value going stale forever if a job stalls. But that insurance has a sharp edge: if materialization stops for good (a pipeline decommissioned, a job silently failing) the online store keeps serving its last computed value as if nothing were wrong, right up until that TTL lapses -- then the key vanishes and a lookup returns null. Serving code almost always treats a missing key as "impute the default," not "raise an error," so the model quietly starts scoring on a placeholder value with no exception anywhere in the stack. No error in the logs is not evidence a feature pipeline is healthy -- only an explicit freshness check is.
And that's the real answer to "isn't this just a database?" A database stores bytes. A feature store adds four things a database won't: point-in-time-correct history, one shared computation path across online and offline, a registry for discovery and lineage, and materialization with freshness monitoring. With all four, training-serving consistency becomes a property of the *system* — not a hope resting on individual engineers remembering to match each other.
Key points
- Establish point-in-time join correctness in your training pipeline before building anything else — this is the hardest invariant to get right and the primary reason feature stores exist. Everything else in a feature store is optimization. The offline store's value is precisely that it can answer "what were this user's features at timestamp T" using only data available before T. Without this guarantee, training datasets contain temporal leakage, offline metrics are inflated, and the gap between evaluation and production performance is structural.
- Trap: materialization lag creates freshness gaps that can be catastrophic for time-sensitive applications. If a feature is computed hourly and a user event happens 50 minutes into the cycle, the online store has 50-minute-old data. For fraud detection, a user who just moved 100K dollars is invisible for up to an hour. Design materialization frequency based on the feature's staleness tolerance — and monitor actual freshness, not scheduled frequency. The schedule and the actual update time are not the same thing.
- Diagnostic: compare feature values your model sees at training time versus at serving time for the same user events. Any systematic difference — even small — is training-serving skew that will silently degrade accuracy. The dual-write pattern makes this check explicit: run the old pipeline and the new feature store simultaneously on real traffic, compare outputs on a sample of entities, and assert identity before cutting over. Do this before the feature store is in the critical path, not after.
- Trap: online-store P99 latency spikes have specific, diagnosable causes. The three usual suspects are a hot key (one popular entity absorbing a disproportionate share of reads, so requests queue behind it), memory pressure that forces the store to evict cached keys under load, and an oversized serialized feature vector that's slow to deserialize on every read. Fix each directly: hash or shard hot keys, add capacity, and put a circuit breaker in front of the store so a latency spike degrades gracefully instead of cascading into the caller.
- Trap: a dead materialization pipeline fails silently, not loudly. Online-store writes are typically set with a TTL a little longer than the materialization interval. If materialization stops for good, the store keeps serving its last good value until that TTL lapses -- then the key vanishes, a lookup returns null, and serving code almost always imputes a default rather than raising an error. A model can degrade for weeks with nothing in the logs to flag it; the only real defense is monitoring a feature_last_updated_timestamp against the freshness SLA, not waiting for an exception that will never come.
A feature store is not storage — it is the infrastructure that makes training-serving consistency a structural property rather than an agreement between engineers who will eventually disagree.
Recap
- Problem: five teams compute "30d avg spend" five ways → silent inconsistency, all models suffer.
- Fix = compute once, serve everyone, through two backends for two jobs.
- Offline store = history, stamped by time → point-in-time "as of T" queries that prevent leakage.
- Online store = latest value only, fast (Redis/DynamoDB/Cassandra), <5ms, sized for latency not storage.
- Registry = governance: definition, owner, freshness SLA, lineage, consumers.
- Materialization = compute + write both stores: batch (Spark/Airflow) or streaming (Kafka→Flink→Redis) when 5-min freshness matters.
- Not "just a database": point-in-time history + one shared path + registry + monitored freshness = consistency as a *system* property.
Check your understanding
Q1. A data scientist wants to reuse "user_30d_purchase_count" computed by another team. Select the two things the feature store provides that make this safe.
- A) The exact computation definition, data type/range, and freshness SLA for the feature, recorded in the registry
- B) A copy of the raw upstream event data so each team can recompute the feature independently with their own join logic
- C) Upstream lineage, current consumers, and the owning team, so breaking changes can be communicated before they ship
- D) A guarantee of identical values only when both teams use the same model architecture and framework version
Q2. Your online store (Redis) is serving a feature at 3ms P50 but 800ms P99. What is causing this and how do you fix it?
- A) P99 spiking to 800ms while P50 stays at 3ms indicates a thundering-herd retry storm overloading the connection pool at exactly 14:00 UTC — all requests queue and P50 itself is misleading
- B) The feature value is too large to serialize under Redis's default 512MB string limit; compress all feature vectors with LZ4 before storing them
- C) Likely causes: a hot-key problem on popular entities, memory pressure causing evictions, or large vector deserialization; fix with key hashing, added capacity, and a circuit breaker
- D) Redis P99 spikes are always caused by stop-the-world garbage collection pauses in the JVM client; upgrade to a Redis version with concurrent mark-and-sweep GC
Q3. Describe the materialisation pipeline for a feature "user_last_7d_app_opens" that needs to be available in the online store with <5 minute staleness.
- A) Run a daily Spark batch job at 3am UTC that recomputes the 7-day count for all 40 million users and writes to Redis; 5-minute staleness is achievable if the job runs every 5 minutes
- B) Stream events through Kafka to a Flink job maintaining a rolling 7-day count, writing to Redis every 5 minutes and to S3 with timestamps for point-in-time training joins
- C) Maintain a running total in the primary Postgres application database behind a connection pool of 200, and read it directly at serving time; database reads are fast enough to meet the 5-minute SLA
- D) A 5-minute staleness SLA requires a dedicated in-memory compute cluster running Apache Ignite; Redis alone, lacking built-in TTL support, cannot guarantee sub-5-minute freshness
Q4. A feature was deprecated 3 months ago but a model in production still uses it. The feature computation pipeline was shut down. What is the failure mode and how do you prevent it?
- A) The model will immediately throw a key-not-found exception when the feature is missing from the Redis keyspace after its 30-day TTL expires, causing a clear and detectable outage
- B) The pipeline shutdown has no effect on production because feature values are cached inside the model's ONNX graph at export time and never re-fetched from Redis
- C) Redis serves the last computed value until its TTL expires, then returns null; the model silently imputes null as default, causing gradual degradation with no exception thrown
- D) The failure mode only occurs if the model was retrained after the pipeline shutdown using the new registry schema version; models never read stale feature values from Redis
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 →