Data Pipeline Scalability and Performance Questions
Diagnosing and fixing throughput, cost, and capacity problems in data pipelines: batch ETL/ELT jobs and streaming ingestion that must handle growing data volume within cost and SLA constraints. Covers bottleneck diagnosis (I/O-bound versus CPU-bound stages, profiling a slow pipeline); partition and key design as a load-distribution DECISION for a processing job (choosing keys, avoiding and mitigating hot partitions); recognizing when a transformation forces an expensive shuffle or join and choosing a mitigation strategy at the decision level, not engine-internal tuning; capacity planning anchored in real data volume (cluster, storage, and network sizing from an event rate and payload profile); and throughput techniques including incremental processing, result caching and materialization, storage-format and compaction choices for throughput, and cost-efficient scaling (autoscaling policy, spot/reserved capacity trade-offs). Also covers multi-tenant resource governance for shared data-processing infrastructure (quotas, isolation, throughput enforcement). Distinct from distributed-data-processing-with-spark-and-hadoop (engine-internal execution mechanics of a specific framework), data-reliability-and-fault-tolerance (correctness and consistency guarantees under failure, e.g. exactly-once semantics and checkpointing), workflow-orchestration-and-scheduling (DAG/dependency coordination), and general application or service performance tuning (web request latency, generic distributed-systems architecture with no data-pipeline framing).
List the principles for choosing a partition key in a distributed processing system (a Kafka topic, a data warehouse table, or a similarly partitioned store). Explain how key choice affects consumer/reader parallelism, data locality, the ability to do stateful joins or aggregations, and the risk of hot partitions. Then propose a specific partition key for each of three different workloads: (a) per-user analytics events, (b) high-volume device telemetry, and (c) ordered financial transactions that must preserve per-account order, and justify each choice.
Sample Answer
Direct answer. A good partition key spreads records evenly across partitions (avoiding hot spots), keeps records that need to be processed together in the same partition (data locality for joins/aggregations), and matches the access pattern consumers will actually use. No single key is universally right; the choice depends on what you need to preserve (ordering, locality) versus what you need to avoid (skew).
Structured elaboration. Four properties determine whether a key choice is good:
- Cardinality and distribution. The key space needs enough distinct values that no single value dominates a large share of traffic. High cardinality alone is not sufficient if the distribution is skewed (a Zipfian distribution -- where a small number of values, like a handful of power users, account for a disproportionate share of activity, unlike a uniform spread across all IDs -- can still create hot partitions even with millions of distinct user IDs).
- Parallelism ceiling. The number of distinct partitions a consumer group can meaningfully process in parallel is bounded by the number of partitions, so the key must support splitting into at least as many partitions as you need consumer parallelism.
- Locality requirement. If downstream logic needs to see all events for a given entity together (a stateful join, a per-user session count, an ordered sequence), the key must group those events onto the same partition; keying on something unrelated (e.g., random/round-robin) breaks that guarantee even though it perfectly avoids skew.
- Hot-partition risk. Any key correlated with a small number of highly active entities (a small set of power users, one large enterprise account, a popular device model) risks concentrating load onto few partitions.
Worked example. Three workloads, three different right answers:
- (a) Per-user analytics events, where downstream needs per-user session windows: key by
user_id. This gives locality (all of one user's events land together, enabling stateful per-user aggregation) at the cost of skew risk if a small number of users are far more active than most -- acceptable here because analytics aggregation is usually resilient to a slightly slower partition, and the locality requirement dominates. - (b) High-volume device telemetry, where no cross-event ordering or locality is required, just even spread for maximum throughput: key by a hash of (device_id, time bucket) rather than raw
device_idalone. This breaks any single device's traffic across multiple partitions over time, avoiding a hot partition even from an unusually chatty device, since no downstream logic needs a single device's records co-located. - (c) Ordered financial transactions per account, where correctness REQUIRES strict per-account ordering: key by
account_id, accepting the skew risk as a hard constraint rather than a trade-off, because reordering transactions to fix skew (e.g., via salting) would violate the ordering guarantee the business requires. Skew mitigation here has to come from elsewhere (dedicated partitions or rate limits for known high-volume accounts), not from changing the key.
Trade-offs & pitfalls. The (c) case is the one candidates most often get wrong: the instinct to "just salt the key to fix skew" is correct for (b) but actively breaks correctness for (c), where ordering is the point of the key choice. The general rule: identify whether the workload has a genuine locality/ordering REQUIREMENT before optimizing for even distribution; if it does, skew mitigation must come from operational techniques (dedicated hot-key handling, rate limiting) rather than from changing what the key groups together.
Define throughput and latency in the context of a large-scale data pipeline. As a Solutions Architect advising a client building a near-real-time analytics pipeline with a 5-second end-to-end SLA, explain how you would prioritize throughput versus latency, list concrete operational metrics you would monitor (both system-level and business-level), and describe at least two architectural decisions that directly affect each metric.
Sample Answer
Direct answer. Throughput is how much work a pipeline completes per unit time (events/sec, bytes/sec); latency is how long any SINGLE unit of work takes end-to-end. The two are related but distinct, and optimizing one can actively hurt the other -- a near-real-time analytics pipeline with a tight end-to-end SLA has to prioritize deliberately rather than assume maximizing throughput automatically also minimizes latency.
Structured elaboration. The classic tension: batching improves throughput (amortizing per-operation overhead across many records) but adds latency (a record waits for its batch to fill before being processed); conversely, processing every record immediately minimizes that record's own latency but sacrifices the efficiency gains of batching, capping achievable throughput. For a 5-second end-to-end SLA, the prioritization question is really: does the SLA apply to EVERY record (a hard per-record latency bound) or to the pipeline's typical/aggregate behavior (a looser target that tolerates occasional slower records as long as throughput keeps up)? These call for different designs.
Concrete operational metrics. System-level: end-to-end latency percentiles (p50/p95/p99, not just average, since averages hide the tail that actually breaches a hard SLA), throughput (events/sec sustained, and separately, PEAK events/sec the system must absorb), queue depth/consumer lag (an early-warning signal that latency is about to degrade before it shows up in the latency metric itself). Business-level: freshness of the data a downstream consumer actually sees (which can diverge from raw pipeline latency if there is caching or batching in a later stage), and SLA-breach rate (the percentage of records/requests that miss the 5-second target, which is often more actionable for stakeholders than a raw percentile number).
Two architectural decisions and their effect on each metric. (1) Batch size / micro-batch interval: a smaller interval directly reduces per-record latency (records wait less time to be batched) but reduces achievable throughput per unit of compute (more, smaller batches means more per-batch overhead relative to the data processed) -- this is the single most direct latency-vs-throughput lever in most streaming architectures. (2) Parallelism / partition count: increasing parallelism generally increases achievable throughput (more work happens concurrently) and, up to a point, can also REDUCE latency (each partition processes less data per unit time, so an individual record's queueing delay shrinks) -- but past a certain point, coordination overhead (cross-partition joins, shuffle, rebalancing) starts adding latency of its own, so more parallelism is not a free latency win indefinitely.
Worked example. A 5-second end-to-end SLA with average throughput of 50,000 events/sec: choosing a 1-second micro-batch interval (rather than, say, 4 seconds) leaves 4 seconds of margin within the SLA for downstream processing and network transit, directly prioritizing latency headroom; if instead throughput were the binding constraint (the cluster genuinely cannot sustain 50,000 events/sec at a 1-second batch interval without falling behind), a longer batch interval trades some of that latency margin for the throughput efficiency needed to keep up, and the team must decide explicitly which of the two the business actually needs protected.
Trade-offs & pitfalls. A pipeline that looks healthy on AVERAGE throughput and AVERAGE latency can still be breaching a per-record SLA regularly if the distribution has a heavy tail -- always monitor and alert on percentiles (p95/p99), not averages, for both metrics. The instinct to 'just add more capacity' to improve both throughput and latency simultaneously works up to a point, but past the point where coordination/shuffle overhead dominates, adding capacity can make BOTH worse at once, which is a useful diagnostic signal that the bottleneck has shifted from raw resource scarcity to coordination cost.
Write pseudocode for a capacity-aware autoscaler that scales streaming consumer pods based on a combination of consumer lag, CPU utilization, and cost. The algorithm should prioritize meeting latency SLAs, avoid flapping (provide cooldown logic), and consider warm-up time for new pods. Outline parameters, thresholds, and how state is persisted between evaluation intervals.
Sample Answer
Direct answer. A capacity-aware autoscaler for streaming consumers needs to combine three signals -- consumer lag (the primary latency-SLA signal), CPU utilization (a secondary resource-pressure signal), and cost (a constraint, not a trigger) -- into one scaling decision, applying cooldown logic to avoid flapping and accounting for new-pod warm-up time so the algorithm does not scale down capacity that has not finished coming online yet, nor double-scale while waiting for pods it already added to become effective.
Structured elaboration and code. The algorithm evaluates on a fixed interval, computes a target replica count from lag and CPU independently (against the current total replica count, not a warm-up-discounted count, so a still-warming pod is never treated as "missing" capacity that inflates the next target), takes the MAX of the two signals (either alone justifies scaling up), applies a cost ceiling, a cooldown gate for scale-up, and explicitly DEFERS any scale-down decision while pods from a recent scale-up are still warming up.
import time
class CapacityAwareAutoscaler:
def __init__(self, min_replicas=2, max_replicas=50, target_lag_seconds=10,
target_cpu_pct=70, cost_per_replica_hour=0.5, max_hourly_cost=15.0,
scale_up_cooldown_s=60, scale_down_cooldown_s=300, warmup_s=45):
self.min_replicas = min_replicas
self.max_replicas = max_replicas
self.target_lag_seconds = target_lag_seconds
self.target_cpu_pct = target_cpu_pct
self.cost_per_replica_hour = cost_per_replica_hour
self.max_hourly_cost = max_hourly_cost
self.scale_up_cooldown_s = scale_up_cooldown_s
self.scale_down_cooldown_s = scale_down_cooldown_s
self.warmup_s = warmup_s
self.current_replicas = min_replicas
self.last_scale_up_ts = float("-inf")
self.last_scale_down_ts = float("-inf")
self.pending_pods = []
def _warming_up(self, now):
return any(ready_at > now for ready_at in self.pending_pods)
def _target_from_lag(self, current_lag_s, replicas_for_calc):
if current_lag_s <= 0:
return replicas_for_calc
ratio = current_lag_s / max(self.target_lag_seconds, 1e-6)
return max(self.min_replicas, round(replicas_for_calc * ratio))
def _target_from_cpu(self, current_cpu_pct, replicas_for_calc):
if current_cpu_pct <= 0:
return replicas_for_calc
ratio = current_cpu_pct / max(self.target_cpu_pct, 1e-6)
return max(self.min_replicas, round(replicas_for_calc * ratio))
def evaluate(self, now, current_lag_s, current_cpu_pct):
self.pending_pods = [t for t in self.pending_pods if t > now]
warming_up = self._warming_up(now)
# Use CURRENT total replicas (not warm-up-discounted) as the basis for the target
# calculation, so newly-added-but-not-yet-ready pods are not double-penalized as
# "missing" capacity when deciding the NEXT target.
target_lag = self._target_from_lag(current_lag_s, self.current_replicas)
target_cpu = self._target_from_cpu(current_cpu_pct, self.current_replicas)
target = max(target_lag, target_cpu, self.min_replicas)
target = min(target, self.max_replicas)
max_affordable = int(self.max_hourly_cost / self.cost_per_replica_hour)
target = min(target, max_affordable)
if target > self.current_replicas:
if now - self.last_scale_up_ts < self.scale_up_cooldown_s:
return self.current_replicas, "scale-up suppressed by cooldown"
added = target - self.current_replicas
for _ in range(added):
self.pending_pods.append(now + self.warmup_s)
self.current_replicas = target
self.last_scale_up_ts = now
return target, f"scaled up to {target} (lag={current_lag_s}s cpu={current_cpu_pct}%)"
elif target < self.current_replicas:
if warming_up:
return self.current_replicas, "scale-down deferred: pods still warming up from a recent scale-up"
if now - self.last_scale_down_ts < self.scale_down_cooldown_s:
return self.current_replicas, "scale-down suppressed by cooldown"
self.current_replicas = target
self.last_scale_down_ts = now
return target, f"scaled down to {target} (lag={current_lag_s}s cpu={current_cpu_pct}%)"
return self.current_replicas, "no change"
# --- usage ---
if __name__ == "__main__":
a = CapacityAwareAutoscaler(min_replicas=2, max_replicas=20)
events = [
(0, 5, 40),
(10, 60, 85),
(20, 55, 80),
(75, 50, 78),
(400, 3, 20),
]
for now, lag, cpu in events:
replicas, note = a.evaluate(now, lag, cpu)
print(f"t={now:>4} lag={lag:>3}s cpu={cpu:>3}% -> replicas={replicas:>2} ({note})")
Executed output (verified, exact script above, unmodified):
t= 0 lag= 5s cpu= 40% -> replicas= 2 (no change)
t= 10 lag= 60s cpu= 85% -> replicas=12 (scaled up to 12 (lag=60s cpu=85%))
t= 20 lag= 55s cpu= 80% -> replicas=12 (scale-up suppressed by cooldown)
t= 75 lag= 50s cpu= 78% -> replicas=20 (scaled up to 20 (lag=50s cpu=78%))
t= 400 lag= 3s cpu= 20% -> replicas= 6 (scaled down to 6 (lag=3s cpu=20%))
At t=10 the algorithm correctly scales up in response to the lag/CPU spike. At t=20, only 10 seconds after the t=10 scale-up (under the 60s scale-up cooldown), a further scale-up is correctly suppressed. At t=75 (65 seconds after t=10, past the cooldown), the still-elevated lag/CPU pushes the target to the configured max_replicas cap of 20. At t=400, after sustained low lag/CPU and with all warm-up windows long expired, the system scales down to 6.
Parameters, thresholds, and state persistence (the three things this question explicitly asks to outline). The tunable PARAMETERS are the constructor arguments above: min_replicas/max_replicas (hard floor and ceiling on replica count), target_lag_seconds and target_cpu_pct (the two per-signal targets the proportional controller scales toward), cost_per_replica_hour and max_hourly_cost (the cost ceiling that caps the target regardless of what lag/CPU alone would justify), and scale_up_cooldown_s/scale_down_cooldown_s/warmup_s (the three timing knobs). THRESHOLDS are set deliberately and asymmetrically: target_lag_seconds=10 is not a hard trip-wire but the level the proportional formula scales toward continuously (further above it scales up more aggressively); scale_up_cooldown_s=60 is short because a lagging SLA needs a fast reaction, while scale_down_cooldown_s=300 is five times longer, because scaling down too eagerly right after relief risks flapping back up on the next minor fluctuation -- fast-up, slow-down is the same asymmetric-cooldown principle a Kubernetes-based Spark Streaming autoscaler would use for the identical reason.
State persistence between evaluation intervals is the one requirement the code above only satisfies incidentally. current_replicas, the two cooldown timestamps, and pending_pods are kept as plain in-memory instance attributes, which works ONLY because this simulation runs all five evaluations inside one continuously-running Python process. A real autoscaler controller is not guaranteed that: if the controller pod restarts (a deploy, a crash, a node eviction) between evaluation intervals, in-memory-only state loses the cooldown timestamps and the pending-pod warm-up list, and the very next evaluation after the restart would incorrectly behave as if no scaling event had ever happened -- functionally the same failure as the 0.0-vs--inf bug below, except caused by a restart rather than a bad default. The fix is to persist this small amount of state externally between evaluation intervals: writing current_replicas, last_scale_up_ts, last_scale_down_ts, and pending_pods to a Kubernetes custom-resource status field (the natural choice if the autoscaler is itself a K8s controller) or a lightweight external store (a Redis key, an etcd entry) that survives a controller restart, and reading it back at the START of each evaluation rather than assuming it is still resident in memory. This also matters if the autoscaler controller itself ever runs as more than one replica for its own availability: without shared, externally-persisted state, two autoscaler replicas could each independently believe no recent scaling event has happened and both act, defeating the cooldown logic entirely.
A real bug this exposed, worth keeping as a teaching point. An earlier version of this code initialized last_scale_up_ts and last_scale_down_ts to 0.0 rather than negative infinity. Running it produced a materially different (and wrong) trace: the very first scale-up attempt at t=10 was incorrectly SUPPRESSED, because 10 - 0.0 = 10, which is less than the 60-second cooldown -- the cooldown gate was comparing against an arbitrary epoch-zero timestamp rather than "no prior scale-up has ever happened," silently delaying the system's first-ever response to load. A second, subtler bug surfaced once that was fixed: computing the scale-DOWN target from the warm-up-discounted effective replica count (rather than the true current replica count) made recently-added, still-warming pods look like a capacity SHORTFALL relative to the naive target formula, triggering an immediate, premature scale-DOWN of a pod that had not even finished coming online. The shipped code above fixes both: cooldown timestamps initialize to negative infinity so the first legitimate scaling decision is never spuriously gated, and any scale-down decision is explicitly deferred (not silently recalculated differently) while pods from a recent scale-up are still warming up.
Trade-offs & pitfalls. The proportional-control target calculation (replicas * ratio) assumes roughly linear scaling of throughput with replica count, which breaks down for workloads with significant coordination overhead -- a production version should be tuned against the workload's measured scaling curve rather than assumed linear. Both bugs found above are members of the SAME general defect class in autoscalers: a cooldown or safety mechanism that is only tested against the SPECIFIC scenario it was designed for (steady-state oscillation prevention) can silently misbehave at a BOUNDARY condition (system startup, or mid-warm-up) that was not part of the original test scenario -- exercising an autoscaler's cold-start and warm-up-window behavior explicitly, not just its steady-state flapping behavior, is what caught both issues here.
Design a cross-team resource isolation strategy for a shared Kubernetes cluster used by multiple business units with diverse workloads (batch ETL, real-time streams, and ad-hoc analytics) and varying SLAs. Describe namespace and quota planning, node pools (preemptible vs reserved), pod priority and preemption settings, network policies, resource guarantees/limits, and cost allocation strategies to prevent noisy neighbors and enforce SLAs.
Sample Answer
Direct answer. A cross-team resource-isolation strategy for a shared cluster running diverse data workloads (batch ETL, real-time streams, ad-hoc analytics) needs three layers working together: quota/namespace planning that gives each team a guaranteed floor, scheduling controls (node pools, priority classes) that prevent one workload type from starving another, and a cost-allocation model that makes the shared cost visible and attributable, since without visibility, isolation alone does not stop resource contention from becoming a recurring political problem.
Structured elaboration.
- Namespace and quota planning. Give each business unit or workload class its own namespace with explicit resource quotas (CPU, memory, and where relevant, GPU or specialized hardware requests/limits), so no single team's job can consume the whole cluster's capacity even accidentally. Quotas should reflect each team's actual baseline need (informed by historical usage), not an arbitrary equal split.
- Node pools. Separate node pools for fundamentally different workload shapes -- e.g., preemptible/spot-backed pools for batch ETL (tolerant of interruption) versus reserved, always-on pools for latency-sensitive real-time streams -- so a batch job's resource usage pattern cannot degrade a stream job's SLA by sharing the same physical nodes.
- Pod priority and preemption / QoS classes. Assign priority classes (e.g., Kubernetes-style Guaranteed/Burstable/BestEffort, or an equivalent) so that under genuine resource pressure, the scheduler preempts lower-priority, more elastic workloads (ad-hoc analytics) before higher-priority, SLA-bound ones (real-time streams).
- Network policies and admission control. Restrict cross-namespace network access to only what is explicitly needed, and use admission control -- resource-request validation (the check that directly prevents a misconfigured job from requesting unbounded resources) alongside pod disruption budgets (a separate control limiting how many replicas of a workload can be voluntarily taken down at once, e.g. during node maintenance, so isolation survives routine cluster operations too) -- rather than relying only on quotas to catch a bad request after the fact.
- Cost allocation. Tag every resource by owning team/workload, and report cost/utilization back to each team regularly -- visibility itself changes behavior (teams right-size their own requests once they see the bill), and it is the only way to fairly resolve disputes about who caused a contention incident.
- Softer controls for ad-hoc/exploratory workloads. Query limits, result caching, and self-service sandboxes for ad-hoc analytics reduce the NEED for hard quota enforcement in the common case, reserving hard preemption for genuine overload rather than every exploratory query.
Worked example. Three workload classes share a cluster: batch ETL (tolerant of delay, given a Burstable/preemptible node pool with generous but not guaranteed quota), real-time streaming (SLA-bound, given a Guaranteed-priority reserved node pool sized to its steady-state need plus headroom), and ad-hoc analytics (unpredictable, exploratory, given BestEffort priority, per-query resource limits, and a self-service sandbox with its own smaller quota so a runaway ad-hoc query cannot starve either of the other two classes). Under a genuine resource crunch, the scheduler preempts BestEffort ad-hoc jobs first, then Burstable batch jobs if needed, while Guaranteed streaming jobs are protected -- and monthly cost reports attributed by namespace let each team see and manage their own consumption rather than treating the shared cluster's bill as an opaque, uncontrollable cost.
Trade-offs & pitfalls. Overly generous quotas defeat the purpose of isolation (everyone requests the maximum 'just in case,' recreating the noisy-neighbor problem the quotas were meant to prevent), while overly strict quotas cause legitimate spikes to be throttled or preempted unnecessarily, generating support tickets and eroding trust in the shared-cluster model -- quotas should be revisited periodically against ACTUAL usage data, not set once and forgotten. GPU isolation for mixed CPU/GPU workloads (a variant of this same problem in ML-heavy environments) needs its own explicit accounting, since GPU resources typically cannot be fractionally shared as cleanly as CPU/memory, making GPU-specific node pools and scheduling almost mandatory rather than optional. Fair-scheduling and burst-policy enforcement for a customer-facing multi-tenant SERVICE (as opposed to internal business-unit isolation) adds a further requirement: per-tenant rate limits and burst budgets enforced at the API/ingestion boundary, since internal quota mechanisms alone do not protect against an external customer's traffic burst the way they protect against an internal team's job.
Your Kafka topic is keyed by user_id, but a small percentage of users generate the large majority of events, causing severe hotspotting on a few partitions. Design a partitioning and operational strategy to mitigate the hotspots while preserving per-user ordering where it is required. Evaluate at least three concrete options and the trade-off each one makes.
Sample Answer
Direct answer. A small number of users generating a large share of traffic creates a hot partition because a Kafka-style keyed topic maps each key deterministically to one partition; the fix has to either change how those specific keys map to partitions (without breaking per-user ordering where it is required) or isolate the heavy keys onto dedicated infrastructure so they stop competing with everyone else's traffic.
Structured elaboration. Three concrete options, in order of how much they change the system:
- Key salting with ordered shards. Append a bounded suffix to the hot key (e.g.,
user_id->user_id_0..user_id_7) so one user's traffic spreads across a fixed number of sub-partitions instead of one. If per-user ORDER matters, route each individual user consistently to the SAME shard (not randomly per message), which preserves per-user ordering while still giving up to 8x parallelism for that user's traffic. This is the right default when some ordering is needed but not global ordering across all of a user's shards simultaneously. - Dedicated hot-partition / heavy-hitter topics. Identify the small set of consistently heavy keys (the classic 1%-of-users-produce-50%-of-events shape) and route them to a separate topic or dedicated partition pool sized for their traffic, leaving the shared topic's partitioning scheme undisturbed for the long tail. This isolates the operational blast radius (a heavy tenant's traffic spike cannot starve everyone else) at the cost of added routing complexity at the producer or a routing layer.
- Dynamic/adaptive re-partitioning. For traffic patterns that shift which keys are hot over time (rather than a fixed known set), a monitoring-driven system that detects a partition crossing a throughput threshold and automatically applies salting or reassignment to just that key, without requiring a full topic redesign every time a new hot key emerges. The trade-off: this is the most operationally complex of the three options to build and trust, since it requires its own detection logic, a threshold tuned to avoid flapping (reassigning back and forth as traffic hovers near the boundary), and it risks triggering a reassignment (and the consumer-group rebalance that comes with it) exactly while the key is under peak load, which is the worst moment for a brief consumption pause. Reserve it for cases where the hot-key set genuinely rotates; if the heavy hitters are stable, option 2's static isolation gets the same benefit with far less machinery.
Worked example. A Kafka topic keyed by user_id shows 1% of users generating 50% of events. Detection: per-partition throughput dashboard shows 4 of 200 partitions running 8-10x the median. Applying salting with 8 ordered shards to the identified heavy users only (not the whole keyspace, to avoid unnecessary complexity for the 99% of users who are not hot) spreads their traffic across those shards; since each shard is consistently assigned per user (hash of user_id selects the shard, then that shard's actual partition is deterministic), per-user ordering is preserved within a shard. Measuring effectiveness: re-check the per-partition throughput ratio after the change and confirm it drops back toward the median, and separately verify downstream aggregations that expected all of a user's events in one partition still produce correct results (if any existed, they now need to merge across that user's shards).
Trade-offs & pitfalls. Consistent hashing with virtual nodes is a related but distinct technique worth naming: rather than salting individual hot keys, it changes how ALL keys map to partitions so that adding or removing partitions moves only a small fraction of keys rather than reshuffling everything -- useful when a partition count itself needs to change (adding or removing partitions while moving only a small fraction of keys) but does not by itself fix a skewed KEY distribution the way salting or heavy-hitter isolation does; they solve different problems and are sometimes confused. Whatever mitigation is chosen, measuring whether it worked by watching aggregate throughput ALONE is a common trap -- aggregate throughput can look fine while a downstream aggregation silently produces wrong results because ordering was broken; always add a correctness check alongside the performance check.
Unlock Full Question Bank
Get access to all 24 Data Pipeline Scalability and Performance interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.