Stage 1 spilled 3.4 GB to disk across 120 tasks. Disk I/O is 10–100× slower than RAM — this stage is paying a heavy I/O tax on every shuffle write.
→ Raise spark.executor.memory so the execution pool fits the working set, or reduce spark.executor.cores to lower concurrent memory pressure.
spark.executor.memory=8g # or reduce cores to lower concurrent peak demand: # spark.executor.cores=2
| disk_spill_bytes | 3.4 GB |
| memory_spill_bytes | 12.1 GB |
| stage_duration_ms | 87000 |
Top-5% of tasks hold 71% of stage time. The largest task ran for 52 s while the median was 3 s (p95/p50 ratio: 17×). Shuffle skew detected — the data distribution is uneven across reduce partitions.
→ Enable AQE skew-join handling so Spark automatically splits skewed partitions at runtime.
spark.sql.adaptive.enabled=true spark.sql.adaptive.skewJoin.enabled=true
| p95_task_ms | 52000 |
| p50_task_ms | 3000 |
| p95_p50_ratio | 17.3 |
| top5pct_share | 71% |
Stage 2 contains a SortMergeJoin but autoBroadcastJoinThreshold is set to -1, disabling broadcast entirely. The right side of the join (estimated 280 MB) could be broadcast instead of shuffled.
→ Remove the broadcast disable or raise the threshold so small tables are broadcast automatically.
spark.sql.autoBroadcastJoinThreshold=314572800 # 300 MB
import org.apache.spark.sql.functions.broadcast df_large.join(broadcast(df_small), "key")
| right_side_bytes | 280 MB |
| shuffle_written | 1.1 GB |
Java serialization is 10× slower than Kryo and produces 2–10× larger byte arrays. Every JVM shuffle write, broadcast variable, and RDD persist pays this cost.
→ Switch to Kryo for JVM workloads. Register your domain classes for maximum performance.
spark.serializer=org.apache.spark.serializer.KryoSerializer
spark.conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
// optionally: spark.conf.set("spark.kryo.registrationRequired", "false")
AQE automatically coalesces shuffle partitions, handles skew, and switches join strategies at runtime. Disabling it forces static planning.
→ Enable AQE for all production workloads on Spark 3.x.
spark.sql.adaptive.enabled=true