Data Pipeline Scalability and Performance Questions
Design data pipelines that meet throughput and latency targets at large scale. Topics include capacity planning, partitioning and sharding strategies, parallelism and concurrency, batching and windowing trade offs, network and I O bottlenecks, replication and load balancing, resource isolation, autoscaling patterns, and techniques for maintaining performance as data volume grows by orders of magnitude. Include approaches for benchmarking, backpressure management, cost versus performance trade offs, and strategies to avoid hot spots.
MediumTechnical
39 practiced
Your Kafka + Flink consumers report sustained high CPU and falling throughput. Provide an investigation checklist to determine whether the CPU is due to (a) inefficient user code, (b) expensive deserialization, (c) GC, (d) network/IO waits, or (e) partition hotspots. For each item list the commands, logs, or metrics you would collect.
Sample Answer
Start with baseline data: collect timestamped system metrics and application-level metrics for the time window (top / htop, vmstat 1, iostat -x 1, sar -n DEV, sar -q, free -m), Flink TaskManager / JobManager metrics, Kafka consumer metrics (group offsets, fetch/bytes metrics), and container/host logs.1) Inefficient user code (CPU hot loops, allocations)- What to collect: - Java thread CPU stacks / profiled hotspots - Flame graphs / CPU profiler traces- Commands / tools: - jstack <pid> > threaddump.txt (multiple samples 5–10s apart) - async-profiler or YourKit/Flight Recorder: run CPU profile for 30–60s, generate flamegraph - perf top / perf record -g (native)- Metrics/logs: - Flink operator metrics: processTime, userFunctionLatency, backpressure ratio - JVM CPU% per thread (top -H -p <pid>)- Interpretation: - Repeated Java stack frames in top CPU-consuming threads → optimize user function, reduce allocations, batch work, avoid sync/blocking.2) Expensive deserialization- What to collect: - Time spent deserializing per record/batch and allocation profiles during deserialization- Commands / tools: - Enable and collect Flink / Kafka client deserialization metrics (e.g., kafka.consumer.Fetcher metrics, Flink's record deserialization/serialization metrics) - Profile CPU and allocate traces focused on deserializer classes (async-profiler --alloc or async-profiler --cpu) - Instrument code to log deserialization latency for samples- Metrics/logs: - Kafka: records-consumed-rate, bytes-consumed-rate, fetch-size, fetch-latency - Flink: numRecordsIn / numRecordsOut and operator userLatency breakdown- Interpretation: - High proportion of CPU time spent in deserializer methods or high per-record deserialization latency → switch to faster serializer (Avro/Protobuf with generated code), use schema registry, reuse objects.3) GC-related issues- What to collect: - GC logs, GC pause/throughput metrics, heap usage over time- Commands / tools: - Enable -Xlog:gc*:file=gc.log:time, or GC logging flags for your JVM (G1/ZGC specifics) - jstat -gcutil <pid> 1 - jcmd <pid> GC.heap_info / GC.class_histogram - Visualize with GCViewer or GCeasy- Metrics/logs: - JVM GC time percentage, number of full GCs, pause durations - Flink JVM metrics: heapUsed, heapCommitted, gcTime- Interpretation: - Frequent long GC pauses or high GC CPU time → tune GC, increase heap, reduce allocation rate, use object reuse.4) Network / IO waits (blocking on socket or disk)- What to collect: - System IO and network counters, thread stack traces showing IO syscalls- Commands / tools: - iostat -x 1, sar -n DEV 1, ifstat 1 - ss -tin or netstat -tnp to inspect socket states and retransmits - tcpdump -w capture.pcap 'port 9092' (brief, sample) - strace -p <pid> -f -tt -e trace=network,read,write (careful in prod) - jstack samples to see threads stuck in java.net.SocketInputStream.read or blocking IO- Metrics/logs: - Kafka client network metrics: fetch-latency, requests-in-flight, io-time - Disk metrics if local RocksDB/state backend: disk queue length, throughput, latencies- Interpretation: - High kernel-level IO wait, threads blocked on read/write → check network saturation, broker interop, disk contention; scale network, tune sockets, increase parallelism.5) Partition hotspots (uneven partition load causes some threads high CPU)- What to collect: - Per-partition consumption rates, consumer lag per partition, task parallelism mapping- Commands / tools: - kafka-consumer-groups.sh --bootstrap-server <broker> --group <group> --describe - Use monitoring (Prometheus) metrics: kafka_consumer_lag{partition}, consumer_fetch_rate_per_partition - Flink metric: operator chain mapping to Kafka partitions, number of subtasks handling partitions - Inspect partition to task mapping in Flink (which subtask reads which partition)- Metrics/logs: - Per-partition records/sec and lag; CPU per TaskManager thread/subtask- Interpretation: - Few partitions with high records/sec or lag → add more partitions, increase parallelism or rebalance consumers, use key-round robin or custom partitioner.Quick triage order:1. Collect CPU profiles (async-profiler / flamegraphs) + jstack samples — gives fastest signal (user code vs deserialization vs IO).2. Check GC logs/JMX metrics — identify GC stalls.3. Check system-level IO/network metrics and socket states.4. Correlate with Kafka per-partition metrics and Flink operator metrics to detect hotspots.Report findings with timestamps, sample profiles, and recommended fixes (code optimization, deserializer change, GC tuning, network fixes, repartitioning).
MediumTechnical
34 practiced
A Spark job joins two 1TB datasets causing heavy shuffle and frequent OOMs. The job aggregates metrics by user_id and writes results. Propose a prioritized set of optimizations (code changes, Spark config, and infra changes) to reduce shuffle, memory pressure, and runtime, and explain the expected impact of each.
Sample Answer
Clarify assumptions: both tables ~1TB each, join on user_id, then aggregate metrics per user_id. Prioritized optimizations (quick wins first):1) Reduce data before join (highest impact)- Filter and project: apply WHERE predicates and select only needed columns on both sides (pushdown). Expected impact: smaller shuffle keys and payload → less memory and network I/O, often >50% reduction.- Pre-aggregate / map-side combine: if one side can be reduced (e.g., session events per user), aggregate before join. Impact: drastically fewer rows to shuffle.2) Change join strategy / keys- Use broadcast join if one side becomes small (or use bloom filter): broadcast smaller table or use DataFrame.broadcast. Impact: eliminates shuffle for the small side.- Repartition both datasets by user_id (co-partition) with a controlled number of partitions before join: df.repartition(numPartitions, "user_id"). Impact: balanced shuffle and reduces skew hotspots.3) Handle skew- Detect skewed keys; apply salting for hot user_ids or use adaptive query execution (AQE) and skewJoinEnabled. Impact: prevents a few tasks from OOMing and long tails.4) Spark config tuning- Enable AQE (spark.sql.adaptive.enabled=true) and skew handling (spark.sql.adaptive.skewJoin.enabled=true).- Increase shuffle partitions appropriately (spark.sql.shuffle.partitions) — set to (total_cores * 3–5) or tune experimentally to avoid huge tasks.- Tune executor memory and off-heap (spark.memory.fraction, spark.memory.storageFraction, spark.memory.offHeap.enabled).Expected impact: fewer long-running tasks, better memory utilization, fewer OOMs.5) Serialization and file format- Use efficient serializers: spark.serializer=org.apache.spark.serializer.KryoSerializer and register classes. Impact: lower JVM overhead and network serialization cost.- Store intermediate/full inputs in columnar compressed formats (Parquet ORC) with predicate pushdown. Impact: faster I/O, less data read into Spark.6) Infra changes (if still needed)- Increase executors or use larger instance types (more memory per executor) rather than many small ones to reduce garbage collection overhead.- Use faster network (placement in same rack/VPC), SSD-backed disks for shuffle. Impact: reduces disk spill latency and network shuffle time.7) Operational/observability- Add metrics/logging: track task skew, peak memory per task, shuffle read/write sizes; run with spark UI to iterate.- Run small-scale experiments to find optimal numPartitions and executor sizing.Trade-offs: More partitions increase task overhead; salting increases intermediate data. Prioritize data reduction and pre-aggregation, then adaptive execution and serialization, then infra scaling. Together these should reduce shuffle volume, reduce spills/OOMs, and shorten runtime significantly.
HardTechnical
37 practiced
Design a consumer-group partition-assignment algorithm for a streaming framework that adapts to heterogeneous consumer resources (CPU/memory) and minimizes rebalance churn. Describe the assignment scoring function, data structures, how to handle joins/leaves of consumers incrementally, and complexity analysis.
Sample Answer
Requirements & goals:- Balance partition assignment according to consumer capacity (CPU/memory), prefer stickiness to minimize rebalance churn, support incremental joins/leaves, be efficient for large groups.High-level approach:- Treat each partition as unit load with weight (e.g., messages/sec or bytes/sec). Each consumer reports a capacity vector C = (cpu_units, mem_units), normalized to a single capacity score cap_i. Assign partitions to consumers so assigned_load_i ≤ cap_i and minimize total reassignment from previous generation (stickiness).Assignment scoring function:- Score(consumer, partition) = α * (remaining_capacity_ratio) - β * reassignment_penalty - remaining_capacity_ratio = (cap_i - load_i) / cap_i (higher = preferred) - reassignment_penalty = 1 if partition was previously assigned to consumer else 0 - α >> β to prefer capacity fit but keep small bias toward previous owner - Optionally incorporate affinity: locality, rack-awareness.Data structures:- Sorted consumer priority queue (heap) keyed by score or remaining_capacity_ratio.- For partitions: list with weight and previous_owner.- Consumer struct: id, cap, current_load, assigned_partitions(set), last_update_timestamp.- Indexes: partition → current_owner, owner history map.Incremental join/leave handling:- Join: 1. Add consumer with reported cap. 2. Recompute target loads: target_i = total_load * cap_i / sum_caps. 3. Greedy steal: low-overloaded consumers donate highest-cost partitions (largest weight or ones with no reassignment penalty) to new consumer until its load ≈ target_new or no suitable partitions. 4. Use scoring function to choose which partitions to move; move minimal count to reach target.- Leave/fail: 1. Mark consumer failed and collect its partitions. 2. For each orphan partition, push into PQ keyed by Score(consumer, partition) computed using current loads; assign to best-fit consumer while respecting cap (or temporarily allow slight overcommit with background rebalancing).- Periodic rebalancing: - Background low-frequency global rebalance to correct drift, running bin-packing (greedy or bounded first-fit decreasing), but only move partitions where benefit outweighs reassignment cost.Complexity:- Join: O(k log n) where k partitions moved, n consumers (heap ops). Worst-case k = P partitions → O(P log n).- Leave: O(m log n) where m orphan partitions.- Periodic global rebalance: O(P log n) with greedy FFD; can be heavier if optimal (NP-hard).Trade-offs and notes:- Prefers stickiness via reassignment penalty to reduce churn.- Uses capacity-proportional targets for heterogeneity.- Tune α/β and thresholds to trade rebalancing aggressiveness vs. latency of imbalance.- For very large scale, shard groups by range and run assignment per shard to parallelize.
MediumTechnical
38 practiced
You must produce a benchmark plan before a 100x traffic increase. Describe what to measure (e.g., throughput, P95/P99 latency, error rates), data generation strategies, tools to use (e.g., custom producers, Locust, JMeter), workload patterns (steady, spike, chaos), and how to interpret results to inform capacity and architecture changes.
Sample Answer
Framework: treat this as an experiment — define goals, design workloads, run controlled tests, analyze results, iterate.What to measure (primary + secondary):- Throughput: requests/sec, messages/sec, DB qps.- Latency distribution: P50, P95, P99, P999 and tail shapes.- Error rates: 4xx, 5xx, timeouts, circuit-breaker trips.- Resource utilization: CPU, memory, network, disk I/O, GC pause time.- Queue/backpressure: queue lengths, consumer lag.- SLA violations and SLO breach rate.- Business metrics: successful transactions/minute, revenue-impacting failures.Data generation strategies:- Use production-like payloads (size, headers, DB records). Sanitize and sample production traces for realism.- Scale data volume: synthetic user IDs, realistic distribution of object sizes, and indexing patterns.- Warm caches and CDNs to steady-state before measurement.- Introduce long-tail payloads and malformed inputs to test validation paths.Tools and workload patterns:- Tools: Locust or k6 for HTTP scenarios (scriptable, distributed), JMeter for protocol variety, Gatling for high-concurrency Scala-based tests. Use custom producers/consumers for message systems (Kafka producer scripts), and iperf for network. Use Prometheus + Grafana for metrics, Jaeger/Zipkin for tracing, and ELK for logs.- Workloads: - Steady-state at target X100 throughput to measure sustained capacity. - Spike tests: sudden ramp-up (10x-100x in seconds/minutes) to test autoscaling and backpressure. - Soak tests: multi-hour/day at elevated traffic to reveal memory leaks and degradation. - Chaos/partial-failure: kill instances, add latency to DB, simulate network partitions. - Gradual ramp (step test) to find breaking points and non-linear behaviors.How to run:- Start with baseline: current prod load, confirm telemetry.- Run step-wise load increases (e.g., 2x, 5x, 10x...) to map linearity and inflection points.- Repeat each scenario multiple times; isolate variables (one change per run).- Ensure environment parity: use production-like infra (same instance types, autoscaling rules).Interpreting results to inform capacity & architecture:- If latency scales linearly and resource utilization < 70% at target, scale horizontally using same architecture and add instances + autoscaling buffers.- If latency tail (P99) spikes while median stays acceptable, investigate queueing, head-of-line blocking, thread-pool saturation, GC — prioritize reducing contention and introducing timeouts/circuit-breakers.- High CPU with low I/O suggests code-path optimization or more CPU cores; high I/O or DB bottleneck suggests caching, read replicas, query optimization, or moving heavy work to async/background jobs.- Persistent errors on spike -> strengthen backpressure (rate-limit, queue-depth limits), graceful degradation (feature gates), and retry/backoff strategies.- Memory leaks or resource exhaustion on soak -> fix leaks, add better resource limits, improve observability and rolling restarts.- For message systems: consumer lag growth means scale consumers or partition count; if partition throughput limits, redesign partitioning or shard by business key.Deliverables:- Benchmark plan doc with scenarios, scripts, metrics to collect, pass/fail criteria.- Dashboard templates (graphs for throughput, latencies, error rates, CPU/memory).- Runbook actions for observed failure modes (scale, throttle, rollback).- Concrete capacity recommendation: instance count/types, autoscaling thresholds, cache sizes, DB replica counts, and architectural changes prioritized by effort vs. impact.This approach gives actionable, reproducible data to decide whether to tweak capacity, optimize code/queries, or introduce architectural changes (caching, async processing, sharding, circuit breakers).
MediumTechnical
36 practiced
You detect a hot partition in Kafka: one partition has extremely high ingress and consumer lag vs others. Programmatically how would you detect such hot partitions, and what safe remediation steps (partition reassignment, key salting, adding partitions, producer-side changes) would you take to minimize downtime and ordering disruption?
Sample Answer
Detecting hot partitions programmatically:- Monitor per-partition metrics: incoming bytes/sec, messages/sec, consumer lag (latest_offset - committed_offset), request rate, and leader CPU.- Flag outliers using statistical thresholds (e.g., z-score or percentile) or fixed multipliers vs cluster median.Example detection (Python):Safe remediation sequence (minimize downtime & ordering disruption):1. Quick mitigation (producer-side) - Temporarily throttle producers for the problematic key(s). - Introduce batching or increase linger.ms to reduce requests. - If feasible, route best-effort traffic to other topics/partitions.2. Short-term partition redistribution - Reassign leaders using preferred leader election so the heavy leader moves to a less-loaded broker. Use Kafka's preferred replica election or kafka-reassign-partitions tool with a carefully generated JSON. - Do one partition at a time; monitor impact.3. Partition-level changes - Add partitions: only safe if you can tolerate rekeying semantic changes. Adding partitions changes mapping for new keys only — it won't redistribute existing keys. Useful if load is because of many keys hashing to same partition and you can migrate producers to new keys. - Repartitioning/migration: create a new topic with desired partitioning, migrate consumers/producers gradually, replicate messages, then cutover.4. Key-level changes (ordering-aware) - Key salting: append small salt to keys to spread load, but this breaks per-key ordering. Use only for keys where ordering is not required or maintain per-salted-subkey ordering downstream by using composite key (originalKey + salt) and an aggregator to restore order. - Producer-side routing: map hot keys to a set of virtual partitions and handle ordering via a small stateful proxy/dispatcher that ensures per-key sequencing.Operational best practices and trade-offs:- Always perform changes incrementally and during low-traffic windows.- Prefer leader moves and producer throttling first — minimal ordering impact.- Key salting trades ordering for throughput; only use when acceptable or when you implement reorder-reconciliation.- Recreating topics and migrating preserves ordering but requires client changes and orchestration.- Automate detection + alerting and run canary tests after each remediation step.Metrics to validate success: per-partition ingress/egress, consumer lag, end-to-end latency, broker CPU/IO.
python
import numpy as np
def detect_hot_partitions(metrics, z_thresh=3.0):
# metrics: dict partition_id -> {'bytes_in':..., 'msgs_in':..., 'lag':...}
vals = np.array([m['bytes_in'] for m in metrics.values()])
med = np.median(vals); std = vals.std() or 1
hot = []
for pid, m in metrics.items():
z = (m['bytes_in'] - med)/std
if z >= z_thresh or m['lag'] > median_lag(metrics)*5:
hot.append((pid, m, z))
return hot
def median_lag(metrics):
return np.median([m['lag'] for m in metrics.values()])Unlock Full Question Bank
Get access to hundreds of Data Pipeline Scalability and Performance interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.