The levers that decide how fast a Spark job runs: let AQE re-plan at runtime, pick the right join, and stop reading data you don't need.
Spark performance tuning is removing the work the engine doesn't need to do — by skipping files, shuffling less, joining smart, and letting Adaptive Query Execution re-optimize the plan from real runtime stats.
Most slow Spark jobs are not CPU-bound — they are data-movement-bound: oversized shuffles, one straggler task on skewed data, or a sort-merge join where a broadcast would have done. Tuning targets those, and AQE handles many of them automatically once you understand what it is doing.
Re-optimization during execution using runtime stats: coalesces shuffle partitions, switches sort-merge to broadcast, and splits skewed partitions. On by default.
Broadcast ships the small side to every executor (no shuffle); sort-merge shuffles and sorts both sides. Auto-broadcast triggers under autoBroadcastJoinThreshold (10 MB).
A few keys hold most rows, so one task runs far longer than the rest. AQE's skew join splits and replicates skewed partitions into evenly sized tasks.
A shuffle redistributes data across the network between stages; when a task's data exceeds memory it spills to disk — the signal to repartition or shrink the shuffle.
Disk cache auto-caches remote Parquet on local SSD on the first read; cache()/persist() manually pin any DataFrame in memory. Prefer disk cache.
Data skipping uses per-file min/max stats (first 32 cols) to skip files; dynamic file pruning pushes a join's filter down to skip files at runtime.
PySparkfrom pyspark.sql.functions import broadcast
orders = spark.table("orders") # large fact table
regions = spark.table("regions") # small dimension
# force a broadcast hash join, skipping the shuffle on the small side
joined = orders.join(broadcast(regions), "region_id")
# SQL equivalent: SELECT /*+ BROADCAST(regions) */ ... FROM orders JOIN regions ...
autoBroadcastJoinThreshold default exists for a reason; raise it deliberately.BROADCAST hint is a suggestion, not a guarantee, and hint precedence is BROADCAST > MERGE > SHUFFLE_HASH > SHUFFLE_REPLICATE_NL.skewedPartitionThresholdInBytes (256 MB) and 5× the median — tiny-but-uneven skew slips through.cache() is lazy and memory-bound; over-caching evicts other blocks and can hurt. On Databricks prefer the automatic disk cache for Parquet/Delta.broadcast(df)."Verified against docs.databricks.com · June 2026