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 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.
What is Change Data Capture (CDC)? Name at least two commonly used CDC tools and outline how you would integrate CDC events into a downstream feature pipeline while preserving low latency and correctness.
Sample Answer
Direct answer: Change Data Capture (CDC) is a technique for continuously capturing row-level insert, update, and delete events from a source database (typically by reading its transaction/write-ahead log) and streaming them to downstream consumers, rather than periodically polling or bulk-exporting the whole table.
Structured elaboration:
- Debezium: an open-source CDC platform that reads a database's write-ahead log (Postgres's logical replication slots, MySQL's binlog, etc.) and publishes row-level change events to Kafka; it is the most widely used open-source option and supports many source databases through connectors.
- AWS DMS (Database Migration Service): a managed AWS service that supports both one-time bulk migration and ongoing CDC replication from a source database to a target (which can be another database, S3, or a stream); convenient if you are already on AWS and want a managed CDC pipeline without operating Debezium/Kafka Connect yourself.
- Confluent's CDC connectors: source connectors within the Kafka Connect ecosystem (often built on Debezium under the hood) that integrate tightly with a Confluent-managed Kafka deployment.
Integrating CDC into a downstream feature pipeline: the CDC tool publishes a stream of change events (each carrying the changed row's before/after state and the operation type) to a message queue (typically Kafka); a stream processor consumes that change stream, applies any needed transformation (deriving a feature from the changed row, or simply passing through updated field values), and writes the result to the feature store's online and/or offline path. Because CDC captures changes as they commit to the source database's log, latency from a source-table write to a downstream feature update can be in the seconds range, much lower than a periodic batch export. To preserve correctness, the pipeline needs to process change events in the order they were committed per row (most CDC tools preserve per-key ordering) and apply idempotent writes downstream, since CDC connectors typically guarantee at-least-once delivery, not exactly-once.
Trade-offs & pitfalls: CDC adds an operational dependency on the source database's replication mechanism (a logical replication slot in Postgres, for example, must be actively consumed or it can cause the source database's write-ahead log to grow unbounded and eventually threaten the source database's own disk space); a CDC pipeline that falls behind or stops consuming is not just a downstream data-freshness problem, it can become a source-database incident. Schema changes on the source table (a column added or renamed) need to be handled by both the CDC connector and every downstream consumer, since CDC events reflect the source schema directly and a breaking change there propagates immediately to every consumer of the change stream.
Scenario: Your streaming engine's state store is growing without bound and causing OOM and long GC pauses. Describe immediate mitigations and long-term strategies: state compaction, TTL eviction, checkpointing, incremental snapshots, windowing changes, and architectural alternatives (e.g., external state store).
Sample Answer
Direct answer
Immediate mitigations buy time without a code change: shorten the watermark threshold (drops old, rarely-needed state faster) and, if the streaming query supports it, force an early checkpoint/restart with a tighter configuration to release currently-held state. Long-term, the real fix is almost always adding or shortening a TTL (time-to-live) on state that should not live forever, since unbounded state growth is usually a symptom of state accumulating for keys that will never see another matching event, not a sizing problem alone; if the state genuinely needs to outlive what Spark's in-memory/RocksDB state store can hold efficiently, moving to an EXTERNAL state store (or redesigning the windowing to not need unbounded per-key history at all) is the architectural escape valve.
Structured elaboration
Why state grows unbounded in the first place. Spark's Structured Streaming state store holds accumulating per-key state for stateful operations (a windowed aggregation's running totals, a flatMapGroupsWithState custom aggregation's accumulated values, a stream-stream join's buffered unmatched rows) across micro-batches; WITHOUT a watermark (or with one set too generously wide), state for a key is never considered "safe to drop," so keys that appear once and never again still hold their state entry FOREVER, and the state store's total footprint grows monotonically with the number of DISTINCT keys ever seen, not the number of ACTIVE keys at any given time.
Immediate mitigations.
- Shorten the watermark threshold. A watermark that is wider than the REAL lateness distribution needs (sizing it against real data) holds state open longer than necessary; tightening it to match the ACTUAL observed lateness (not an arbitrarily generous default) directly bounds how long a window's state stays open before being released.
- State compaction / a forced checkpoint cycle. RocksDB-backed state stores (the default for larger stateful workloads) accumulate internal fragmentation over many micro-batches; triggering compaction (
spark.sql.streaming.stateStore.rocksdb.compactOnCommitor a similar maintenance operation, depending on Spark version) reclaims space from already-tombstoned (deleted-but-not-yet-reclaimed) state entries without needing a full restart.
Long-term strategies.
- TTL eviction. For stateful operations expressed via
flatMapGroupsWithState/applyInPandasWithState(arbitrary custom stateful logic, this API), explicitly setting a TIMEOUT (event-time or processing-time based) on each group's state ensures a key with no new events for the configured duration has its state EXPLICITLY cleared, rather than relying solely on watermark-driven window closure (which only applies to the built-in windowed-aggregation shape, not arbitrary custom state). - Incremental snapshots. Rather than a full state-store snapshot on every checkpoint (expensive and slow for very large state), Spark's state store supports INCREMENTAL checkpointing (only the DELTA since the last snapshot), reducing both the per-checkpoint cost and the RECOVERY-time cost of replaying from a checkpoint, a meaningful operational win once state size grows large enough that full snapshots themselves become a bottleneck.
- Windowing changes. If the query's CURRENT design needs per-key state to live indefinitely because the business logic is framed as "track this forever," reframing to a BOUNDED window (a rolling 30-day window instead of "all time," for instance) if the business requirement genuinely tolerates it directly bounds state size by construction, the most durable fix when it is available.
- External state store. If the required state genuinely cannot be bounded by any of the above (a business requirement to track something truly indefinitely, at a scale beyond what an in-cluster state store can hold efficiently), moving that state to an EXTERNAL, purpose-built store (a key-value store queried by the stateful operation instead of held in Spark's own state store) is the architectural escape valve, at the cost of added system complexity (network round-trips to the external store per micro-batch, a new dependency to operate) that should be reached for only once the simpler, in-Spark options are genuinely insufficient.
Worked example
A Structured Streaming job tracking "days since a user's last purchase" via flatMapGroupsWithState, keyed by user_id, with NO timeout configured: every user who EVER made a purchase, even years ago and never again, still holds an open state entry, and the state store's total size grows with the CUMULATIVE user count over the query's entire lifetime, not the count of users who are still ACTIVE.
Immediate mitigation: if a watermark-based windowed aggregation were ALSO present in the same query, tightening its threshold helps that PART of the state, but does nothing for the flatMapGroupsWithState state specifically, since watermark-driven closure only applies to the built-in windowed-aggregation shape.
Long-term fix: add an explicit event-time timeout to the flatMapGroupsWithState call (e.g., GroupStateTimeout.EventTimeTimeout with a 180-day threshold): a user with no purchase activity for 180 days has their state entry explicitly cleared on the next batch that crosses that threshold, bounding the state store's size to roughly "distinct users active within the last 180 days," not "every user ever seen," directly addressing the actual root cause (a missing TTL on genuinely stateful, indefinitely-accumulating tracking) rather than just buying time with tighter checkpointing.
Trade-offs and pitfalls
- Common mistake: tightening the watermark and declaring the problem solved, when the actual unbounded growth is coming from a
flatMapGroupsWithStateoperation the watermark does not govern at all; diagnosing WHICH stateful operation is actually accumulating (via the Spark UI's Structured Streaming state-store metrics, which report size PER operator) before choosing a fix avoids applying the wrong lever. - Common mistake: reaching for an external state store as the first response to state growth, when a properly-configured TTL/timeout on the existing state operations would have solved it with far less added complexity; treat the external store as the LAST option once the simpler, in-Spark fixes are confirmed insufficient, not the first instinct.
- A TTL that is too aggressive drops state for a genuinely still-relevant key too early, a correctness cost (losing legitimate history) traded for a memory savings; size the TTL against the REAL business meaning of "this key is no longer relevant," not purely against memory pressure.
- Incremental snapshots reduce checkpoint COST but do not by themselves bound state SIZE; they help the operational overhead of an already-large state store, but do not address the underlying unbounded-growth root cause the way a TTL/timeout does, worth applying alongside a real bounding fix, not as a substitute for one.
You are alerted that a model's AUC fell 10% after an upstream schema change. Construct a step-by-step investigation and remediation plan: backtracking the schema change, running data-quality checks, analyzing distribution drift, deciding between retraining and rollback, and communicating with stakeholders.
Sample Answer
Direct answer: The investigation starts by pinpointing exactly what the schema change altered (backtracking it), checks whether that alteration introduced a data-quality problem or a genuine distribution shift, and only then decides between retraining and rolling back, communicating findings to stakeholders throughout rather than only at the end.
Structured elaboration:
- Backtrack the schema change. Identify precisely what changed (a field's type, a renamed column, a newly-nullable field, a changed encoding) and when it was deployed, correlating the timing against when the AUC (area under the curve) drop was first observed; a schema change that landed shortly before the AUC drop is the natural first hypothesis, but confirm the timing correlation before assuming causation.
- Data-quality checks. Check whether the schema change introduced obviously bad data: a spike in nulls for a field that used to rarely be null, values now out of an expected range, a field silently defaulting to zero or a placeholder because a type coercion failed quietly rather than erroring loudly. These checks are cheap and fast, and a very large fraction of real incidents like this one turn out to be a data-quality problem introduced by the schema change, not a genuine shift in the underlying real-world distribution.
- Distribution drift analysis. If data quality checks come back clean, compare the feature's distribution before and after the schema change (using the same kind of statistical tests covered elsewhere in ML-monitoring contexts, such as population stability index or a KS-test) to determine whether the schema change altered what values the feature can even take (for example, a previously-truncated field now reporting full precision, subtly shifting its distribution) versus the model simply seeing a population it was never trained on for an unrelated reason.
- Retraining vs. rollback decision. If the schema change is a genuine, intentional improvement (more accurate data, a fixed bug in the upstream source) and data quality is otherwise fine, the right fix is usually retraining the model on data reflecting the new, corrected schema, since rolling back would mean reverting a legitimate improvement. If the schema change introduced a data-quality regression (nulls, truncation, a broken join), the right fix is rolling back the schema change (or fixing it forward quickly) and NOT retraining on the bad data, since retraining on corrupted input would just teach the model the corruption rather than fix the underlying problem.
- Stakeholder communication. Communicate early and iteratively: an initial "we've identified a likely cause, investigating further" message once the schema-change correlation is found, rather than waiting until the full root cause and fix are confirmed, since stakeholders (including whoever depends on the model's predictions) need to know as soon as possible whether to trust recent predictions, even before the full resolution is ready.
Worked example: The AUC drop correlates with a schema change that made a previously-required last_login_days field nullable; a data-quality check finds the null rate on that field jumped from under 1% to 15% right after the change, tracing to an upstream service that started omitting the field for a subset of users under a specific new condition; this is a data-quality regression, not a genuine distribution shift, so the fix is coordinating with the upstream team to restore the field (or handling the null explicitly and correctly in the feature pipeline if the upstream omission is actually intentional going forward) rather than retraining the model to accept a 15% null rate as normal.
Trade-offs & pitfalls: Jumping straight to retraining without first distinguishing a data-quality regression from a genuine distribution shift is a common mistake that can mask the real problem: retraining on corrupted data (a spike in nulls treated as "the new normal") often recovers some AUC in the short term while baking in a systematic weakness that resurfaces later or degrades the model's behavior on the affected population specifically. Conversely, assuming every post-schema-change AUC drop must be a data-quality issue and rolling back reflexively, without checking, risks reverting a genuinely beneficial schema fix and leaving the original bug the schema change was meant to address back in place.
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 46 ML Feature Pipelines and Feature Stores interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.