Apache Spark is a distributed processing engine for workloads that are too large, too slow or too operationally complex for a single process. The important production skill is not memorizing APIs; it is understanding how data moves through a distributed execution plan and what makes that plan reliable and efficient.
Mental model
Sources → Spark driver → execution plan → executors → shuffle / storage → curated datasets → downstream consumers
The driver coordinates the application and builds execution plans. Executors perform distributed tasks. Transformations are evaluated lazily, and actions trigger work. Wide transformations may require shuffles, which move data between partitions and are often where performance problems become visible.
DataFrames and Spark SQL
For most modern engineering workloads, DataFrames and Spark SQL provide the clearest optimization path because Spark can reason about the logical and physical plan.
from pyspark.sql import SparkSession
from pyspark.sql import functions as F
spark = SparkSession.builder.appName("orders-etl").getOrCreate()
orders = spark.read.parquet("s3a://raw/orders/")
result = (
orders
.filter(F.col("status") == "COMPLETE")
.groupBy("customer_id")
.agg(F.sum("amount").alias("lifetime_value"))
)
result.write.mode("overwrite").parquet("s3a://curated/customer-value/")
Partitioning and shuffles
Partitioning determines how work is distributed. Too few partitions can under-use the cluster; too many can create scheduling overhead and small files. Data skew can leave one executor processing far more data than the rest.
Useful questions include:
- Is the input partitioned in a way that matches the query pattern?
- Which transformations introduce shuffles?
- Is one key disproportionately common?
- Are joins moving more data than necessary?
- Is output producing an unhealthy number of small files?
result.explain("formatted")
print(result.rdd.getNumPartitions())
Storage formats
Columnar formats such as Parquet are commonly used because analytics workloads often read selected columns rather than entire records. Table formats such as Apache Iceberg or Delta Lake can add metadata, schema evolution and transactional table behavior for lakehouse-style architectures.
| Layer | Typical concern |
|---|---|
| Object / distributed storage | durability, throughput, locality |
| File format | compression, column pruning, statistics |
| Table format | snapshots, schema evolution, partition evolution |
| Spark | compute, joins, aggregation, streaming |
| Catalog / governance | discoverability, ownership, access |
Structured Streaming
Spark Structured Streaming uses the same DataFrame model for continuously arriving data. A common pattern connects Kafka to Spark for stateful transformation and then writes curated outputs to a lakehouse, database or serving layer.
Kafka → Spark Structured Streaming → validation / enrichment → lakehouse or serving store → analytics / applications
Production streaming needs explicit thinking about checkpoints, replay behavior, state size, event-time semantics and sink guarantees.
Performance workflow
Do not tune randomly. Start with evidence:
measure runtime
→ inspect Spark UI / execution plan
→ identify skew, shuffle, spill or I/O bottleneck
→ change one variable
→ rerun representative workload
→ compare
Common improvement areas include predicate pushdown, column pruning, join strategy, partition sizing, adaptive query execution, caching only when reuse justifies it, and avoiding unnecessary Python-side processing.
Production operations
Spark needs the same operational discipline as other distributed systems:
- observable driver and executor behavior
- deterministic deployment configuration
- resource quotas and limits
- dependency/version management
- retry and failure handling
- data-quality checks
- lineage and ownership
- cost and capacity monitoring