Optimization & Performance Tuning Reference
Essential Spark optimization strategies: Broadcast Joins, AQE, Salting, Caching, and Shuffle Partitions.
Broadcasts a small DataFrame to all executor nodes to avoid costly network shuffle during joins.
Used In: Lookup Joins, Dimension-Fact Joins
from pyspark.sql.functions import broadcast
large_df.join(broadcast(small_df), 'user_id')orders_df.join(broadcast(country_lookup_df), 'country_code')Fast BroadcastHashJoin without shuffling large_dfRe-optimizes physical query execution plans at runtime based on actual stage metrics.
Used In: Production Jobs, Dynamic Partition Coalescing, Skew Mitigation
spark.conf.set('spark.sql.adaptive.enabled', 'true')spark.conf.set('spark.sql.adaptive.enabled', 'true')
spark.conf.set('spark.sql.adaptive.coalescePartitions.enabled', 'true')
spark.conf.set('spark.sql.adaptive.skewJoin.enabled', 'true')Dynamic runtime query optimization enabledSolves data skew where 1 key (e.g., NULL or 'UNKNOWN') receives 90% of data, overloading a single reducer partition.
Used In: Handling Skewed Joins, Straggler Mitigation
# Add random salt key to skewed dataframe:
df = df.withColumn('salt', (rand() * 10).cast('int'))
# Join on key AND saltskewed_df.withColumn('salted_key', concat(col('user_id'), lit('_'), floor(rand() * 10)))
lookup_df_exploded = lookup_df.withColumn('salt', explode(array([lit(i) for i in range(10)])))Uniformly distributed partitions without straggler tasksControls the default number of partitions used when shuffling data for joins or aggregations.
Used In: Cluster Tuning, Memory Allocation
spark.conf.set('spark.sql.shuffle.partitions', '200')# For small datasets (100MB):
spark.conf.set('spark.sql.shuffle.partitions', '10')
# For large datasets (1TB):
spark.conf.set('spark.sql.shuffle.partitions', '2000')Adjusted shuffle partition countSkips reading irrelevant partition directories at runtime when joining a large partitioned Fact table with a filtered Dimension table.
Used In: Data Warehouse Joins, Star Schema Queries
spark.conf.set('spark.sql.optimizer.dynamicPartitionPruning.enabled', 'true')# DPP activates automatically when joining on partition keys:
fact_sales.join(dim_date.filter(col('year') == 2024), 'date_id')| date_id | sales | year |
|---|---|---|
| 20241 | 15000.0 | 2024 |
| 20242 | 22000.0 | 2024 |
Pre-sorts and pre-partitions tables on disk by a specific key to eliminate network shuffles during join operations.
Used In: Repeated Joins on Large Tables, Data Mart Optimization
df.write.bucketBy(numBuckets, colName).sortBy(sortCol).saveAsTable(tableName)df1.write.bucketBy(16, 'user_id').sortBy('user_id').saveAsTable('bucketed_users')
df2.write.bucketBy(16, 'user_id').sortBy('user_id').saveAsTable('bucketed_orders')
# Join operates with ZERO network shuffle:
spark.table('bucketed_users').join(spark.table('bucketed_orders'), 'user_id')| user_id | name | order_id |
|---|---|---|
| 1001 | Alice | ORD_99 |
Configures executor JVM memory allocation split between Execution (joins/shuffles) and Storage (caches/broadcasts).
Used In: Executor Tuning, Out-of-Memory Debugging
spark.memory.fraction = 0.6 # Total Spark RAM fraction
spark.memory.storageFraction = 0.5 # Storage fraction of Spark RAM# Submit job tuned for heavy shuffles:
--conf spark.memory.fraction=0.8 \
--conf spark.memory.storageFraction=0.2Memory Configuration Applied:
- spark.memory.fraction: 0.8
- spark.memory.storageFraction: 0.2Replaces Java's slow default object serializer with Kryo serialization for 2x-10x faster serialization speed and smaller memory footprints.
Used In: High-throughput Shuffles, RDD Caching
spark.serializer = org.apache.spark.serializer.KryoSerializerconf = SparkConf()\
.set('spark.serializer', 'org.apache.spark.serializer.KryoSerializer')\
.set('spark.kryoserializer.buffer.max', '1024m')
spark = SparkSession.builder.config(conf=conf).getOrCreate()Serializer Enabled: org.apache.spark.serializer.KryoSerializerAllocates memory outside the Java Virtual Machine (JVM) heap to avoid Java Garbage Collection (GC) pauses on huge datasets.
Used In: Ultra Large Scale Clusters, Garbage Collection Mitigation
spark.memory.offHeap.enabled = true
spark.memory.offHeap.size = 10g--conf spark.memory.offHeap.enabled=true \
--conf spark.memory.offHeap.size=8gOff-Heap Memory Allocator Active (8GB)Calculates optimal executor memory and core configurations to avoid HDFS I/O bottlenecks and GC overhead.
Used In: Production YARN / Kubernetes Spark Submit Tuning
--executor-cores 5 --executor-memory 19g --num-executors 50# Optimal Rule of Thumb:
# 5 Cores per Executor (Max HDFS throughput)
# ~4GB RAM per Core -> 20GB Executor Memory + 10% OverheadCluster Allocation:
- Cores per executor: 5
- Memory per executor: 19GB