Spark and Hadoop Basics Questions
Fundamentals of big data processing with Apache Spark and Apache Hadoop, including core concepts, architecture (HDFS, YARN, MapReduce), Spark components (RDDs, DataFrames, Spark SQL), and basic data pipeline patterns for batch and streaming workloads.
HardTechnical
57 practiced
Explain how to create a custom Partitioner for RDDs in Spark to deterministically distribute keys across partitions. Describe the required methods, how to register and apply the partitioner to pair RDDs, and outline code or pseudocode for a partitioner that hashes a composite key while pinning certain hot keys to fixed partitions for stability.
Sample Answer
Approach: Implement a custom Partitioner by extending org.apache.spark.Partitioner and overriding numPartitions and getPartition. Use partitionBy on PairRDDs (RDD[(K,V)]). To deterministically distribute composite keys, compute a stable hash of constituent fields, modulo numPartitions; for hot keys map them to fixed partition ids to avoid movement and imbalance.Scala sample:Register/apply:- For PairRDD[(K,V)] call rdd.partitionBy(new HotKeyPartitioner(N, hotMap))- For joins, ensure both RDDs are partitioned with the same Partitioner instance (or compatible numPartitions and hotKey mapping) to enable efficient shuffle-free joins.Key points & reasoning:- Override numPartitions/getPartition required by Spark.- Pinning hot keys reduces churn and concentrates hot traffic onto dedicated partitions to avoid re-sharding when cluster topology changes.- Use a stable hash (CRC32 or MurmurHash3 with fixed seed) to ensure deterministic mapping across runs and nodes.- Ensure hotKeyToPartition values are within [0, numPartitions-1] and handle null keys.Complexity and caveats:- getPartition should be fast (O(1)); heavy computation hurts scheduling.- Imbalanced hot-key assignment can overload certain executors; monitor and consider replicating or fan-out strategies.- When changing hot-key mapping or numPartitions, data must be reshuffled; keep mappings versioned and coordinate rollouts.
scala
import org.apache.spark.Partitioner
import java.util.zip.CRC32
class HotKeyPartitioner(val numPartitions: Int, hotKeyToPartition: Map[String, Int]) extends Partitioner {
require(numPartitions > 0)
// Required by Spark
override def numPartitions: Int = this.numPartitions
// key: composite key represented as string or tuple
override def getPartition(key: Any): Int = {
// check hot-key first (string representation or explicit type)
val keyStr = key match {
case s: String => s
case (a, b) => s"$a|$b"
case other => other.toString
}
hotKeyToPartition.get(keyStr) match {
case Some(p) => Math.floorMod(p, numPartitions)
case None =>
// stable 32-bit hash using CRC32 for determinism across JVMs
val crc = new CRC32()
crc.update(keyStr.getBytes("UTF-8"))
(crc.getValue.toInt & 0x7fffffff) % numPartitions
}
}
override def equals(other: Any): Boolean = other match {
case h: HotKeyPartitioner => h.numPartitions == numPartitions && h.hotKeyToPartition == hotKeyToPartition
case _ => false
}
}MediumSystem Design
56 practiced
Given a clickstream dataset with fields user_id, timestamp, page, country and session_id, propose a partitioning and bucketing strategy for long-term storage to balance query performance, small-files risk, and schema evolution. Explain why you selected particular partition columns and whether to use bucketing, and how you would manage compaction.
Sample Answer
Requirements:- Efficient analytical queries by time and geography; ad-hoc user/session lookups- Avoid many small files from high-frequency streams- Support schema evolution (new fields)- Use columnar format (Parquet) for compression and predicate pushdownPartitioning:- Primary partition by event_date (ingest date, YYYY-MM-DD). Rationale: most queries filter by date ranges; keeps partitions manageable and pruneable.- Secondary (optional) by country for high-cardinality cross-country reporting if team runs many country-filtered queries. Use a single-level partitioning in the layout: /event_date=YYYY-MM-DD/country=US/... to avoid explosion.- Do NOT partition by user_id or session_id (very high cardinality → tiny partitions / many small files).Bucketing:- Use bucketing on session_id (or user_id) when you have frequent joins/group-bys by session/user across partitions (e.g., session-level aggregation). Choose fixed number of buckets (e.g., 128 or 256) tuned to cluster size and query concurrency. Bucketing ensures co-location of same session keys, speeds up shuffle-less joins in Spark and reduces skew vs. unbucketed files.- If workloads are purely append + time-range scans without heavy session joins, skip bucketing to reduce write complexity.File format & layout:- Parquet with Snappy compression, write parquet row groups ~128–256MB.- Write atomically into staging and then move into partition path.Compaction & small-files management:- Use micro-batch ingestion (e.g., hourly) to produce medium-size files. Run periodic background compaction jobs: - Small-file compaction per partition (event_date/country): merge small parquet files into target size (e.g., 256MB) using Spark or Glue. Schedule daily or when file count threshold exceeded. - Maintain lineage: write compaction outputs to new path and atomically swap or update Hive/Glue metastore partitions. - For bucketed tables, ensure compaction preserves bucketing (repartition by bucket column and write with same numBuckets).Schema evolution:- Use Parquet and schema evolution features in metastore (Glue/Hive): add-only changes (new nullable columns) are safe. Enforce nullable for new fields.- Maintain schema registry: versioned Avro/Parquet schemas or store schema in Glue to track changes.- For incompatible changes (rename/type change), implement explicit migration jobs that backfill or create new table version and update consumers.Metadata & governance:- Use Hive/Glue metastore or Iceberg/Delta Lake for ACID, time travel, and easier schema evolution. If using Iceberg/Delta, rely on table formats to simplify compaction and schema changes.Operational notes:- Monitor partition sizes, file counts, and query patterns; adjust partition granularity (e.g., YYYY-MM vs YYYY-MM-DD) and bucket count accordingly.- Automate compaction triggers based on thresholds (files per partition, average file size) and validate outputs with checksums.- Provide documentation and examples for downstream users on how to query efficiently (filter by event_date, avoid full-table scans, leverage bucket columns for joins).
HardSystem Design
52 practiced
Design a secure, multi-tenant Spark-on-YARN cluster supporting several teams with different SLAs. Address resource isolation, queue and capacity configuration, authentication and authorization (Kerberos, Ranger/Atlas), data access controls, audit logging, monitoring, and cost allocation for chargeback purposes.
Sample Answer
Requirements:- Multi-tenant Spark-on-YARN for several teams with different SLAs (latency, throughput, cost)- Strong isolation (compute, memory, network), authentication & authorization, data governance, auditing, monitoring, and chargeback.High-level architecture:- YARN cluster with CapacityScheduler + queues per org/team and per SLA tier (gold/silver/batch). NodeLabels for special hardware (GPU/mem-optimized).- Kerberos for cluster authentication (KDC HA). Use Hadoop container-executor with cgroups to enforce per-container CPU/memory limits.- Apache Ranger for fine-grained authorization on HDFS, Hive, Kafka, and Spark (via plugin). Apache Atlas for lineage.- HDFS with POSIX ACLs + Ranger policies; S3-compatible object store signed URLs and bucket policies in cloud.- Spark: use spark-submit with --principal/--keytab, enable spark.yarn.principal and encryption (spark.io.encryption.enabled), enable shuffle service with auth.- Network isolation via Calico/NSX or yarn network policies; isolate sensitive datasets to secure VPC/subnets.Resource isolation & queue config:- CapacityScheduler config: define root -> team queues -> SLA subqueues. Assign guaranteed capacity, max capacity, and preemption (preemption.enabled for gold).- Use queue ACLs to restrict submitters (queue.acl-submit-job) and admins.- NodeLabels to pin heavy workloads to specific nodes; set maximumApplicationsPerUser per queue.- cgroups + yarn.nodemanager.linux-container-executor.cgroups.enabled = true to prevent OOM/CPU steal.Authentication & Authorization:- Kerberos for mutual auth across HDFS, YARN, Spark, Hive, Kafka. Service principals per service.- Ranger with plugins on HDFS/Hive/Atlas/Spark: central policy UI and REST API. Use LDAP/AD for user/group sync.- Spark SQL integration: enable Spark to consult Ranger for SQL-level permissions; block un-authorized UDFs.Data access controls & lineage:- Implement Ranger policies for table/column/row-level masking and data masking plugins for sensitive columns.- Use HDFS encryption zones for PII; S3 server-side encryption and KMS (customer-managed keys).- Atlas captures lineage from Spark jobs (hooks) and metadata; enforce tagging-based policies (tag-based Ranger policies).Audit logging & monitoring:- Enable Ranger audit logs (write to HDFS and streaming to Kafka). Forward to central SIEM (Splunk/Elastic) with alerts for policy violations.- YARN and Spark audit events (application start/stop, container allocation) sent to Kafka -> ELK.- Metrics: Prometheus exporters for YARN, NodeManager, Spark, HDFS; Grafana dashboards per team + SLA SLIs (job latency, queue wait time, resource utilization).- Alerting for SLA breaches (PagerDuty).Chargeback / Cost allocation:- Require job metadata tags: team, project, cost-center (spark.conf spark.app.tags/team=... and yarn.application.tags).- Collect resource usage from YARN RM (memory-seconds, vcore-seconds) and node cost rates; aggregate by tags for daily/weekly reports.- Use Apache Ranger audit + YARN logs to map storage and compute usage; for cloud, use provider billing + cost allocation tags. Provide automated invoices and dashboards.Operational practices & governance:- Quotas per team and per environment; enforce via CapacityScheduler and HDFS quotas.- CI/CD for policies (Ranger policy as code) and automated testing of policies with staging cluster.- Regular key rotation, KDC monitoring, and periodic access reviews.- Run sensitive workloads in dedicated queues/namespaces; use encryption-in-transit and at-rest; restrict interactive notebooks with tokenized gateways and session timeouts.Trade-offs:- Strong isolation (cgroups + node labels) reduces noisy neighbor risk but lowers overall utilization—mitigate with capacity overcommit strategies for noncritical queues.- Kerberos adds operational complexity; mitigate with KDC HA and automation for keytab handling.This design balances security, isolation, governance, and operational visibility while enabling chargeback and SLA differentiation.
HardTechnical
54 practiced
Design a Structured Streaming approach that needs to write to a non-idempotent external system (for example a third-party billing API) while guaranteeing exactly-once semantics. Discuss idempotency wrappers, deduplication using unique event IDs, write-ahead logs or transactional sinks, and practical limitations of two-phase commit across distributed systems.
Sample Answer
Requirements & constraints:- External system is non-idempotent (third‑party billing API).- Need logical exactly‑once: each event billed once even with retries/failures.- Streaming scale, distributed workers, network failures, restarts.Recommended pattern (practical, reliable):1) Enforce a unique event ID upstream (UUID or deterministic hash). Include batch/offset metadata for traceability.2) Durable, atomic staging write: in your Structured Streaming job, write the event + outcome state to a transactional store first (e.g., Kafka topic with exactly‑once producers, or a database with atomic insert/transaction). This is the single source of truth.3) Outbox / write‑ahead: write an "outbox" record that contains payload, event_id, status=READY inside the same atomic transaction that marks the event processed. This prevents losing intent even if the worker crashes after calling API.4) External call with idempotency wrapper: - Use the event_id as an idempotency key to the billing API (if API supports it). If not supported, implement a local idempotency layer: before calling, try to insert event_id into an idempotency table (unique constraint). If insert succeeds, proceed to call; if it fails (duplicate key), skip call or reconcile. - Record the call result and update outbox status to SUCCESS/FAILED atomically.5) Exactly‑once at consumer: have a separate executor that reads READY outbox rows and performs API calls; retry transient failures. Use at‑most‑once vs at‑least‑once tradeoffs resolved by the idempotency table so retries don't double‑bill.6) Periodic reconciliation: background job compares billing provider receipts vs outbox state to fix anomalies.Why not rely on two‑phase commit (2PC) across distributed systems:- 2PC across streaming job and arbitrary third‑party API is impractical: requires the API to be a transactional resource manager — rarely available.- 2PC introduces blocking on coordinator failure, poor scalability, and distributed locking issues.- Distributed transactions across many partitions hurt throughput and availability.Transactional sink options:- Use Kafka with transactional producers + exactly‑once semantics for internal handoff.- Use databases supporting conditional upserts and unique constraints for idempotency.Edge cases & limitations:- If the API is truly non‑idempotent and rejects duplicate idempotency keys, you must ensure durable pre-insert of id key (outbox) before calling.- Long-running or eventual confirmations require compaction/reconciliation windows.- Performance: idempotency checks add latency; scale idempotency table with partitioning/sharding.Summary:Implement an outbox pattern: atomically persist intent + dedupe key, then call the external API guarded by an idempotency check (DB unique constraint or API-provided key). Avoid cross-system 2PC; prefer local transactions + idempotency + reconciliation for practical exactly‑once guarantees.
HardSystem Design
73 practiced
Architect an end-to-end streaming pipeline capable of ingesting and processing 1 million events per second. Include component choices (Kafka, schema registry, Spark Structured Streaming or alternatives), partitioning and topic design, consumer parallelism, backpressure handling, checkpointing, sinks and storage layout, cost/performance trade-offs, and failure and replay strategies for a Data Engineering team.
Sample Answer
Requirements & constraints- Ingest 1M events/s, low-latency processing (~seconds), durable storage, replayability, exactly-once where needed, cloud-managed preferred.High-level architectureProducers -> Kafka (ingest) + Schema Registry -> Stream processor (Flink or Spark Structured Streaming) -> Output sinks (S3 parquet for lake, OLAP store / warehouse for analytics, Kafka DLQ) -> Monitoring & control planeComponent choices & rationale- Kafka (Confluent or MSK): proven for high throughput, retention for replay, partitioning control, quotas.- Confluent Schema Registry (Avro/Protobuf): schema evolution and validation at ingest.- Stream processing: Apache Flink recommended for sustained 1M eps with low-latency backpressure control and native exactly-once via distributed snapshots. Spark Structured Streaming is okay for micro-batch use cases with higher latency and simpler team familiarity.- Storage: S3 (parquet/iceberg) for cost-efficient, queryable lake. Warehouse (BigQuery/Snowflake/Redshift) or OLAP store fed via batch/stream for analytics.Topic & partitioning design- One logical topic per logical event type; avoid mixing unrelated schemas.- Partition key: choose business key when ordering per key is required (user_id, device_id). Otherwise use round-robin for uniform distribution.- Partitions count: target partitions = ceil(1M eps / per-partition throughput). Realistic per-partition throughput ~5-15k events/s depending on message size and broker. For 1M eps, start with 100–300 partitions and scale. Use partition reassignment for growth.- Replication factor 3, min.insync.replicas=2.Producer patterns & validation- Use async batching, compress (snappy/lz4), idempotent producers and transactional writes when cross-topic atomicity required.- Producers validate schemas against registry; push invalid events to a separate “ingest-errors” topic.Consumer parallelism & processing- Flink: scale parallelism = Kafka partitions (1:>=1). Use task slots and parallel consumers per partition. Maintain co-locate parallelism across operators to avoid shuffle where possible.- Use keyBy for keyed state; use operator chaining to reduce latency.- Avoid large operator state per key; use RocksDB state backend for larger state with incremental checkpoints.Backpressure & flow control- Rely on Kafka retention as buffer. Flink provides backpressure signaling; tune task parallelism and resources.- Use Kafka quotas per-producer/consumer to prevent hot-producers from starving cluster.- Autoscale consumer fleet (Kubernetes/HPA) based on lag and CPU.- Implement per-topic/producer throttling and a DLQ for malformed/slow events.Checkpointing & delivery guarantees- Flink: enable checkpointing interval (e.g., 30s), persistent checkpoint storage (S3), enable checkpoint timeout and max concurrent checkpoints. Use Kafka sinks with two-phase commit or Kafka transactions for end-to-end exactly-once.- Spark Structured Streaming: use checkpointLocation on durable storage; for exactly-once sink support prefer idempotent sinks or transactional connectors.- Store offsets in Kafka (consumer committed offsets tied to checkpoint) or use built-in connector that ties checkpoint to offset commit.Sinks & storage layout- S3 (Parquet/ORC/Iceberg): partition by date/hour, event_type, and a hashed prefix for high-cardinality columns to avoid small files. Target file sizes 256MB–1GB; use compaction jobs (Spark/Flink) to reduce small files.- OLAP: stream to warehouse via batched loads (file manifest) or streaming ingestion API for near-real-time analytics.- Hot store: push aggregated metrics to time-series DB (Prometheus/ClickHouse) for dashboards.Failure & replay strategies- Kafka ensures durability with replication; enable rack-awareness.- On processor failure, restore from latest checkpoint; Kafka replay uses offsets retained by checkpoint.- To replay historical intervals: increase retention or use longer retention topics (hot topic for 7–30 days, cold storage via mirrored topics), or export raw events to immutable S3 for long-term replay.- Implement ability to reset consumer group offsets by timestamp or offset and re-run processing job in replay mode. Use idempotent writes or write to separate replay output dataset to avoid contaminating production results.- DLQ pattern: route poison messages to DLQ topic with metadata for debugging and reprocessing.Monitoring, observability & operational controls- Metrics: producer/consumer throughput, consumer lag, broker CPU/io, checkpoint durations, state size, backpressure duration.- Alerts: rising consumer lag, failed checkpoints, broker under-replicated partitions.- Tracing & logs: attach event IDs for tracing; sample traces for high-latency flows.- Runbook for broker failover, partition rebalance, producer throttling and large replays.Cost vs performance trade-offs- More partitions -> higher throughput but more controller overhead and metadata; balance with broker capacity.- Managed Kafka (MSK/Confluent) costs more but reduces operational burden; self-managed needs more ops engineers.- Flink (cluster sizing) vs Spark: Flink yields lower latency and better backpressure control (higher infra tuning complexity). Spark may be cheaper for micro-batch ETL workloads.- Retention sizing: keeping long retention in Kafka is expensive; offload to S3 for cost-effective long-term replay.Key configuration knobs to tune- Broker: num.io.threads, num.network.threads, socket.send.buffer.bytes, log.segment.bytes, compression.type.- Producers: linger.ms, batch.size, max.in.flight.requests, enable.idempotence.- Consumers: fetch.max.bytes, max.poll.records, session.timeout, heartbeat.- Stream processor: checkpoint interval, state backend (RocksDB), parallelism, memory tuning.SummaryUse Kafka + Schema Registry for durable, schema-managed ingestion; Flink for low-latency, backpressure-aware processing with checkpointed exactly-once; S3 (Parquet/Iceberg) + warehouse for storage and analytics. Design partitions to distribute load, keep replication and retention policies for replay, implement DLQs and compaction, autoscale consumers based on lag, and monitor checkpoints/lag closely. This combination balances throughput, durability, replayability, and operational manageability for 1M events/s.
Unlock Full Question Bank
Get access to hundreds of Spark and Hadoop Basics interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.