Optimizing the memory allocated to Apache Spark executors to balance performance, stability, and resource utilization.
Apache Spark’s in-memory processing shines when memory is tuned correctly. Executor memory settings determine how much RAM each executor process can use for cached data, shuffle buffers, user code, and internal Spark structures. If memory is undersized, you’ll see OutOfMemoryError; if oversized, cluster nodes are under-utilized and scheduling becomes inefficient. Proper tuning requires understanding Spark’s memory model, workload characteristics, and cluster hardware.
Since Spark 2.0, Unified Memory Management partitions executor memory into two dynamic regions:
The regions borrow from each other, but only up to limits set by spark.memory.storageFraction
(default 0.5). Unified memory itself occupies spark.memory.fraction
(default 0.6) of the executor JVM heap.
--executor-memory
(or spark.executor.memory
).spark.memory.offHeap.size
; zero unless off-heap enabled.spark.executor.memoryOverhead
or calculated as max(384MB, 0.10 * executorMemory)
.The sum of heap, off-heap, and overhead must fit into the node’s physical RAM minus OS daemons and other services (YARN/Nomad/K8s sidecars).
Poorly chosen executor memory settings lead to:
Conversely, well-tuned memory delivers predictable performance and maximizes cluster throughput.
Start with 3–5 cores per executor to balance parallelism and GC overhead. More cores → fewer executors → higher heap per executor.
--executor-memory
)(node RAM - OS reserve) / executorsPerNode
.overhead = max(384MB, 0.10 * heap)
.heap = available – overhead – offHeap
.Example on a 64 GB node, 4 executors/node, 4 GB OS reserve:
available = (64 - 4) / 4 = 15 GB
heap ≈ 13.5 GB (reserving 1.5 GB overhead)
spark.memory.fraction
(default 0.6) – lower it (e.g., 0.5) if your code holds large on-heap objects like broadcast vars.spark.memory.storageFraction
(default 0.5) – increase if caching datasets is critical; decrease for heavy shuffles.Smaller objects → less heap.
spark.serializer org.apache.spark.serializer.KryoSerializer
spark.kryo.registrationRequired true
spark.sql.inMemoryColumnarStorage.compressed true
Use Spark UI, Ganglia, CloudWatch, or Datadog to track:
Increase heap if spill/OOM persists; decrease if GC > 20% or CPUs idle.
# 8-core node, 32 GB RAM, YARN cluster
# Reserve 2 GB for OS, run 2 executors per node, 4 cores each
spark-submit \ \
--master yarn \
--deploy-mode cluster \
--num-executors 6 \
--executor-cores 4 \
--executor-memory 13G \
--conf spark.executor.memoryOverhead=2G \
--conf spark.memory.fraction=0.55 \
--conf spark.memory.storageFraction=0.4 \
--conf spark.serializer=org.apache.spark.serializer.KryoSerializer \
my_etl_job.jar
This layout leaves ~1 GB node RAM slack for YARN and OS, keeps GC under control, and minimizes shuffle spills.
Galaxy focuses on SQL authoring. When you offload heavy-duty Spark SQL pipelines to a cluster, ensure your spark.sql
workloads are memory-tuned as described. Craft and test your statements in Galaxy, then embed them in Spark jobs with the tuned executor settings for production.
spark.memory.*
fractions.Spark’s promise of in-memory speed fails if executors run out of RAM or spend half their time garbage collecting. Proper tuning prevents job failures, minimizes shuffle spills, and ensures every gigabyte of cluster memory translates into useful work. In multi-tenant environments it also avoids noisy-neighbor syndrome by keeping each job within predictable resource envelopes.
Begin with 4 GB heap, 512 MB overhead per core, then scale based on Spark UI metrics. This usually avoids immediate OOM while leaving room for OS.
When spark.memory.offHeap.enabled=true
, Spark allocates the specified spark.memory.offHeap.size
outside the JVM. Subtract this from available RAM just like overhead.
Larger heaps prolong stop-the-world GC cycles. Instead of raising heap, add more executors or fine-tune GC (e.g., G1, ZGC) if on Java 11+.
Dynamic allocation resizes the number of executors, not the memory per executor. You must still set sensible baseline memory and overhead values.