ML Feature Pipelines and Feature Stores Questions
Data infrastructure for machine learning: feature pipelines, feature stores, online/offline consistency, training-serving skew, and data preparation for models. Covers building reliable feature platforms and preventing leakage in the data path feeding models. The data-engineering-for-ML topic.
Design a feature store that must sustain 100,000 feature writes per second while keeping average online read latency under 50ms. Outline the architecture layers (ingestion, transformation, offline store, online store, materialization jobs), the partitioning strategy, and the online storage technology choice, with emphasis on the write path.
Sample Answer
Direct answer: Sustaining 100,000 writes per second while keeping reads under 50ms means the write path and read path need to be architecturally separated: an ingestion and transformation layer absorbs the write volume and batches it efficiently into the offline store, while materialization jobs push a summarized, read-optimized copy into the online store on a cadence the read SLA (service-level agreement) can tolerate, rather than the online store taking every write synchronously.
Structured elaboration:
flowchart LR
Producers["Producers (100k writes/sec)"] --> Log["Kafka log"]
Log --> Xform["Transform layer"]
Xform --> Offline["Offline store (Parquet/Iceberg)"]
Xform --> Materialize["Materialization job"]
Materialize --> Online["Online store, 20 shards (DynamoDB / Cassandra)"]
Reader["Reads, under 50ms"] --> Online
- Ingestion layer. A message queue (Kafka or similar) absorbs the 100k writes/sec as an append-only log, decoupling producers from the rate the downstream stores can actually sustain and giving replay capability if a downstream consumer falls behind.
- Transformation layer. Stream or micro-batch processors (Flink/Spark Structured Streaming) consume the log, compute or pass through feature values, and write to both the offline store (for training, at full fidelity and volume) and a materialization pipeline feeding the online store.
- Offline store. Columnar, append-friendly storage (Parquet on object storage, or a table format like Iceberg/Delta) handles 100k writes/sec easily since it is optimized for high-throughput sequential writes, not point lookups.
- Online store and materialization. Rather than writing every one of the 100k events/sec directly into the low-latency online store, materialize periodically (e.g. every few seconds to a minute) into the online store's write path, or use an online store designed for high write throughput (a wide-column store like Cassandra, or a managed store like DynamoDB with provisioned write capacity) if genuinely every write must be reflected online quickly.
- Partitioning strategy. Partition by entity ID (consistent hashing) across enough shards on both the write and read side that no single partition absorbs a disproportionate share of the 100k writes/sec; monitor for and rebalance around emerging hot partitions.
- Online storage technology choice. DynamoDB or Cassandra are natural fits here because they are built for high sustained write throughput with horizontal scaling, unlike a single-node Redis instance which would need careful cluster sharding to sustain 100k writes/sec reliably.
Worked example: At 100k writes/sec, if you partition into 20 shards, each shard handles 5,000 writes/sec, which is comfortably within a single Cassandra or DynamoDB partition's sustained write capacity when the partition key is well-distributed (recall DynamoDB's roughly 1,000 WCU per-partition soft limit, so 5,000 writes/sec on one logical shard would actually need to be spread across at least 5 underlying partitions, reinforcing that partition-key design, not just shard count, determines whether the write path holds up).
Trade-offs & pitfalls: A store optimized for 100k writes/sec is not automatically also optimized for sub-50ms reads; the two workloads have different access patterns (append-heavy, roughly uniform writes vs. skewed, latency-sensitive reads), so the same store often needs different tuning (or a different store entirely) for each, which is why materializing into a separate, read-optimized online copy is the standard pattern rather than serving reads directly off the write-optimized store. Under-partitioning the write path is the single most common way this design fails in practice: a partition key that looks well-distributed in aggregate can still have local hot spots (a batch of correlated writes for related entities arriving together), so monitoring per-partition write rates, not just the aggregate, is necessary to catch it before it causes write throttling.
Design an online feature retrieval service to achieve median latency under 5ms at 100,000 requests per second. Cover data store choice, multi-region caching, cache warming, hotspot mitigation, consistency model, load balancing, handling high write throughput from upstream streaming jobs, and how you would measure and maintain tail latency.
Sample Answer
Direct answer: Hitting a 5ms median at 100k requests/sec means the design has to eliminate every unnecessary network hop and put the hot path entirely in memory close to the caller, with the data store itself sharded wide enough that no single shard is the bottleneck. The architecture layers, in order from the client: a local or co-located cache, a sharded low-latency store (Redis Cluster or a DynamoDB-style store), multi-region replicas, and an observability layer that separately tracks median and tail latency because at this scale they diverge.
Structured elaboration:
flowchart LR
Client["Inference client"] --> Region1["Regional read path"]
subgraph Region["Nearest region"]
Region1 --> LocalCache["Local cache"]
LocalCache -->|miss| Shard["Sharded online store (20 shards, 3x replicas)"]
end
Primary["Primary write path"] -->|async replication| Region
Primary -->|async replication| RegionB["Other regions"]
Shard -->|slow path fallback| Stale["Serve slightly stale cached value"]
- Data store and sharding. Partition the keyspace (typically by hashing entity ID) across enough shards that each shard handles a small fraction of 100k QPS (queries per second) with headroom, e.g. 20 shards at ~5k QPS each with 3x replication for read fan-out.
- Multi-region caching. Serve reads from the region closest to the caller. Each region holds a local read replica (or a fully local cache warmed from the primary) so a request never crosses a region boundary on the read path; cross-region replication happens asynchronously in the background.
- Cache warming. On a region failover or a cold cache, warming from a snapshot or from the primary region avoids a thundering herd of cache misses hitting the backing store simultaneously; a common pattern is to replay the last N minutes of writes into the new cache before routing traffic to it.
- Hotspot mitigation. A small number of keys will still dominate traffic (see the hot-key sub-area); mitigate with a local in-process cache for the hottest keys, request coalescing (collapse concurrent identical lookups into one backend call), and, if needed, replicating hot keys onto every shard rather than one.
- Consistency model. Accept eventual consistency between regions (bounded by replication lag, typically tens to low hundreds of milliseconds) in exchange for the latency win; document the staleness bound so consumers know what they are trading.
- Load balancing. Client-side or sidecar-based load balancing with health checks and fast failure detection, so a single slow shard does not drag down the aggregate tail.
- Measuring and maintaining tail latency. Track p50, p95, p99, and p99.9 separately, not just an average; instrument per-shard and per-region so you can find the shard causing the tail rather than only seeing the aggregate. Use load shedding or a fast timeout-plus-fallback (serve a slightly stale cached value) for the small percentage of requests that would otherwise blow the SLA (service-level agreement).
Worked example: At 100k QPS with a target median of 5ms, budget the latency: roughly 1-2ms for the network hop to a co-located cache, 0.5-1ms for the cache lookup itself, and the remainder as safety margin for queueing under load. If each shard node can sustain 8,000 ops/sec at that latency (a realistic number for an in-memory store with pipelining), you need at least ceil(100,000 / 8,000) = 13 shards for the primary write path, and you would typically round up to 16-20 to leave headroom for uneven key distribution and node maintenance.
Trade-offs & pitfalls: The most common mistake at this scale is designing for the median and being surprised by the tail: a 5ms median with a 200ms p99.9 still means thousands of slow requests per second at 100k QPS, which is often unacceptable for a synchronous inference path. A second common mistake is under-provisioning replica read fan-out, so a single popular shard saturates even though the aggregate cluster has spare capacity. Cross-region async replication means a model can read a feature that is a few hundred milliseconds stale right after a write; if the use case cannot tolerate that (for example, a fraud rule keyed on a just-written flag), the design needs a synchronous or quorum-read path for that specific feature instead of the default async replication.
A model has started showing more false positives, and you suspect a mismatch between offline feature computation and online serving retrieval. Describe a plan to detect, reproduce, and fix issues caused by inconsistent feature computation (for example, a stale cache, missing keys, or a serialization difference), including the instrumentation and tests you would add to prevent recurrence.
Sample Answer
Direct answer: Detecting and reproducing a training-serving inconsistency starts by comparing the exact feature values the model saw at training time against what the online path currently returns for the same entities, narrowing down whether the divergence is a stale cache, a missing key, or a serialization mismatch, and closing the loop with instrumentation that would catch this class of bug automatically going forward.
Structured elaboration:
- Detect. Set up a systematic comparison: for a sample of entities, pull their feature values from the offline (training-time) computation and from the online serving path for the same point in time, and diff them; a consistently non-zero diff rate (not just occasional noise) confirms a real inconsistency rather than expected minor timing differences.
- Reproduce. Narrow to specific entities showing the largest divergence and trace each one's value through the pipeline: what did the offline computation produce, what does the online store currently hold, and when was the online value last updated, which starts distinguishing the candidate causes.
- Stale cache. If the online value's last-updated timestamp is old relative to when it should have refreshed, the online materialization pipeline itself may be lagging or failing silently for a subset of entities (worth checking if it correlates with a specific partition or shard); the fix here is addressing the materialization lag/failure, not the feature logic itself.
- Missing keys. If some entities have no online value at all (falling back to a default or null that the offline path would not have produced), trace whether those entities are newly created (a cold-start gap between when an entity is created and when it first gets materialized online) or whether a upstream join or filter is silently excluding them from the online materialization pipeline that the offline pipeline does not exclude them from.
- Serialization differences. If values exist on both sides but differ, check whether the offline and online computation paths are using genuinely the same transformation logic (a common root cause is the two paths having independently-implemented, subtly different logic, e.g. different rounding, different null-handling, or a unit mismatch) versus a serialization/deserialization bug (a type coercion, an encoding mismatch) that corrupts an otherwise-correct value on the way to or from the online store.
- Instrumentation and tests to prevent recurrence. Add an automated, continuously-running online/offline consistency check (not just a one-time investigation) that samples entities and alerts if the divergence rate exceeds a threshold; add a unit or integration test asserting the offline and online computation paths, when fed the same input, produce identical output, so a future code change that lets the two paths drift apart is caught in CI rather than in production months later.
Worked example: The investigation finds that entities with a specific upstream data source (say, a newer mobile client version) show the highest divergence; tracing one such entity reveals the online path's transformation logic was updated recently to handle a new field from that client version, but the corresponding offline (batch) transformation logic was never updated to match, meaning the two paths have silently diverged for exactly the population using the new client, which explains both the false-positive symptom (features computed differently for a subset of the population) and why it was not caught immediately (it only affected a growing but still partial slice of traffic).
Trade-offs & pitfalls: A common trap is fixing the specific instance of divergence found (patching the online or offline logic to match) without addressing why the two paths were able to drift apart in the first place; the durable fix is usually architectural, such as sharing the actual transformation code between the offline and online paths (a single source of truth for the logic, executed in both a batch and a streaming/serving context) rather than maintaining two independently-written implementations that have to be manually kept in sync, which is the root cause this class of bug keeps recurring from in practice.
Explain how Change Data Capture (CDC) can power feature ingestion. Design a CDC-based pipeline that keeps an online feature store up to date with minimal latency, ensures idempotent writes, and handles schema evolution and ordering guarantees on the source table.
Sample Answer
Direct answer: A CDC (change data capture)-based feature-ingestion pipeline reads row-level changes from the source database's log, publishes them to a durable stream, and applies them to the online feature store as idempotent, ordered updates, with explicit handling for schema evolution on the source and per-key ordering guarantees.
Structured elaboration:
- Capture. A CDC connector (Debezium or equivalent) attaches to the source database's log and emits a change event per committed row change, including the operation type (insert/update/delete), the row's new state, and typically the old state for updates.
- Streaming layer. Change events publish to Kafka (or a similar durable log), partitioned by the row's primary key so that all changes to the same entity land in the same partition and are processed in commit order for that entity, which is essential for correctness (an update processed before its preceding update would leave the feature store in a wrong state).
- Transformation and write. A stream processor consumes the change events, derives the feature value (which may be a direct passthrough of a changed column, or a computed transformation), and writes to the online feature store using an idempotent upsert keyed by entity ID, so a redelivered change event (CDC connectors are typically at-least-once) does not corrupt state, matching the general idempotent-write pattern used elsewhere in this topic.
- Minimizing latency. Because CDC events are emitted as soon as the source transaction commits (not on a polling interval), end-to-end latency from a source write to an updated online feature is typically dominated by the streaming pipeline's own processing time (commonly low single-digit seconds), not by the capture mechanism.
- Schema evolution handling. Register the source table's schema in a schema registry as part of the CDC pipeline, and configure the registry for backward-compatible evolution (new nullable columns are safe; renaming or type-changing an existing column is not, and needs a coordinated migration); the transformation layer should fail loudly (not silently drop or misinterpret) on an incompatible schema change rather than writing corrupted feature values.
- Ordering guarantees. Rely on Kafka's per-partition ordering (guaranteed) combined with partitioning by entity ID (so all of one entity's changes are strictly ordered) to ensure the feature store never applies an older update after a newer one for the same entity, which a naive multi-partition setup without a consistent partition key would not guarantee.
Worked example: For a user_profile table where an email_verified column update needs to become a feature within seconds, the CDC connector emits a change event immediately on commit; the stream processor, partitioned by user_id, applies an idempotent upsert to the online store's user_id -> email_verified mapping. If the same change event is redelivered (a connector restart, for example), the upsert simply writes the identical value again, with no observable effect, which is exactly the idempotency property needed to safely tolerate at-least-once delivery.
Trade-offs & pitfalls: A CDC pipeline that is not partitioned by entity ID (for example, round-robin partitioning for load-balancing purposes) breaks the ordering guarantee silently: two updates to the same entity can land in different partitions and be processed out of order by different consumer instances, producing a feature store that reflects an older state even though a newer one was written later; this is one of the most common CDC-pipeline correctness bugs and is easy to miss in testing since it only manifests under genuine concurrent load. Backward-incompatible source-schema changes (a column rename) that are not caught by the schema registry's compatibility check can silently produce nulls or garbled values in the feature store rather than an obvious failure, which is why enforcing compatibility checks at the registry, not hoping every source-database change goes through a coordinated process, is the more robust design.
A website's click events arrive at 50,000 events per second. Analytics needs hourly aggregates, but an online recommender needs features fresher than 5 seconds. Compare batch and streaming ingestion architectures for this workload, discussing trade-offs in latency, cost, operational complexity, state management, and fault tolerance, and give a recommendation.
Sample Answer
Direct answer: At 50,000 click events per second with two very different downstream needs (hourly aggregates for analytics, sub-5-second freshness for a recommender), the right design runs both a streaming path and a batch path off the same ingested event log, rather than forcing one architecture to serve both needs.
Structured elaboration:
- Ingestion. All 50,000 events/sec land in a durable log (Kafka), decoupling ingestion rate from either downstream consumer's processing rate and giving replay capability for both paths.
- Streaming path (for the recommender). A stream processor consumes the log continuously, computing the near-real-time features (e.g. a short rolling window of recent activity) and writing them to a low-latency online store, meeting the sub-5-second freshness requirement.
- Batch path (for hourly analytics). The same log (or a copy of the raw events landed in object storage) is processed hourly by a batch job that computes the coarser aggregates, writing to the offline/analytics store; this path can be simpler and cheaper since it does not need to meet a tight latency bound.
- State management. The streaming path needs bounded, checkpointed state for its rolling windows (as covered in the windowing/watermarking sub-area of this topic); the batch path largely avoids persistent state since each run processes a complete, bounded chunk of data.
- Fault tolerance. The streaming path needs checkpointing and a fast-recovery story (a few seconds of downtime is tolerable, a few minutes may violate the freshness SLA (service-level agreement)); the batch path's fault tolerance is simpler (a failed run is retried from the last successful checkpoint or rerun entirely for that hour).
Worked example: At 50,000 events/sec, an hourly batch job processes roughly 180 million events per run; a well-provisioned Spark job on a modestly sized cluster comfortably aggregates that volume within a small fraction of the hour it has available, leaving generous headroom for retries. The streaming path processing the same 50,000 events/sec continuously needs enough parallelism (partitioned consumers) that no single task falls behind the source rate, which is a much tighter, always-on capacity requirement than the batch path's "finish comfortably within an hour" bar.
Trade-offs & pitfalls: Running both a streaming and a batch path off the same source (a lambda-architecture-style split) means the two paths' feature-computation logic can drift apart over time if not carefully shared or tested against each other, producing subtly different numbers for what is conceptually "the same" underlying metric at different time granularities; a common mitigation is sharing the core transformation logic between both paths where possible, and monitoring for large divergence between the streaming path's rolled-up hourly total and the batch path's independently-computed hourly total as a correctness check.
Unlock Full Question Bank
Get access to all 49 ML Feature Pipelines and Feature Stores interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.