Symptoms
ExecutorLostFailure (executor 12 exited caused by one of the running tasks) Reason: Container killed ... exceeding memory limits/ exit code 137.java.lang.OutOfMemoryError: Java heap spaceorGC overhead limit exceeded.org.apache.spark.shuffle.FetchFailedExceptionand stage retries.Total size of serialized results ... is bigger than spark.driver.maxResultSize.
1. Find where it fails
Spark UI → Stages → the failed stage. Look at:
- Task duration and input size quartiles: max far above median = skew.
- Shuffle read/write size and spill (memory/disk): heavy spill = partitions too large.
- The failure reason in the failed task's row.
2. Map the error to a fix
| Error | Usually means | Quick fix |
|---|---|---|
Driver OOM, maxResultSize | collect(), toPandas(), huge broadcast | Don't collect; write to storage; lower broadcast threshold |
| Executor OOM / exit 137 | Partitions too big, skewed key, wide rows, UDF memory | More shuffle partitions, enable AQE skew join, bigger executors |
FetchFailedException | Executor holding shuffle files died (often OOM or spot loss) | Fix the OOM; avoid spot for shuffle-heavy stages |
| Python worker killed | pandas UDF / Python memory outside JVM | Raise memory overhead, smaller batches (arrow.maxRecordsPerBatch) |
3. Quick configuration fixes
spark.conf.set("spark.sql.adaptive.enabled", "true")
spark.conf.set("spark.sql.adaptive.skewJoin.enabled", "true")
spark.conf.set("spark.sql.adaptive.coalescePartitions.enabled", "true")
spark.conf.set("spark.sql.shuffle.partitions", "800") # or "auto" on Databricks
spark.conf.set("spark.sql.autoBroadcastJoinThreshold", str(64 * 1024 * 1024))
Executor-level settings (set at cluster/session start, not at runtime):
spark.executor.memory 16g
spark.executor.memoryOverhead 4g # more for PySpark / native libraries
spark.executor.cores 4
Fewer cores per executor gives each task more memory.
Tip
On Databricks, switching to a memory-optimised node type with the same total cores is often the fastest way to get tonight's run through. Then fix the code tomorrow.
4. Fix skew in code
Find the hot key:
df.groupBy("customer_id").count().orderBy("count", ascending=False).show(10)
Options: filter or handle NULL/default keys separately (a classic hot key), broadcast the small side, or salt the key:
from pyspark.sql import functions as F
N = 16
big = big.withColumn("salt", (F.rand() * N).cast("int"))
small = small.withColumn("salt", F.explode(F.array([F.lit(i) for i in range(N)])))
joined = big.join(small, ["customer_id", "salt"])
5. Re-run safely
Make sure the job is idempotent (overwrite partition or MERGE) before re-running, so a partial previous attempt doesn't leave duplicates.
Prevent it next time
- Track stage spill and max task time per run; alert when they grow.
- Load test with production-size data; data growth is what usually tips a job over.
- Avoid
collect()in production code paths; review UDFs for memory use.