Spark为何发生磁盘溢写?大资源配置下的溢写疑问
Spark Shuffle全量磁盘溢写原因分析
环境配置梳理
- 集群资源:18个Executor,单Executor配置102GB内存、26核,集群总内存1836GB、总核数468
- Spark内存参数:
--conf "spark.memory.fraction=0.6" --conf "spark.memory.storageFraction=0.1" - 内存配额计算:单Executor的Spark统一内存池为
102GB * 0.6 = 61.2GB,其中执行内存约61.2GB * (1-0.1) = 55.08GB,集群总执行内存约18 * 55.08GB ≈ 991GB(注:原计算存在偏差,此处按参数逻辑修正) - 作业特征:大型Join触发全量Shuffle,Shuffle写入压缩后534.9GB、未压缩3.9TB,使用Kryo序列化,无缓存/广播操作
全量溢写的核心原因
1. Shuffle写的内存分配逻辑限制
Spark的执行内存(Execution Memory)主要用于Shuffle读阶段的聚合、Join计算,并非用于Shuffle写阶段的输出缓存。Shuffle写依赖的是独立的小缓冲区:
- 默认
spark.shuffle.file.buffer=32KB,每个ShuffleWriter仅持有该大小的缓冲区,数据满额后直接刷盘,缓冲区总量极小,无法承载GB级Shuffle数据。 - Shuffle写的核心逻辑是按分区排序后刷盘,设计上就不会将大量Shuffle输出长期驻留内存。
2. 单Task的内存配额不足
集群总内存充足,但单个Task的可分配内存有限:
- 每个Executor运行26个并行Task(与核数一致),单Task可分配的执行内存约
55.08GB / 26 ≈ 2.1GB。 - 单Task需处理的未压缩数据约
3.9TB / 468 ≈ 8.3GB,压缩后约1.3GB,接近甚至超过单Task内存配额,触发内存溢出并全量刷盘。
3. SortShuffleManager的默认行为
Spark 3.x默认使用SortShuffleManager,其流程决定了Shuffle写必然刷盘:
- 若Shuffle分区数超过
spark.shuffle.sort.bypassMergeThreshold=200,Task会将数据写入内存排序缓冲区,满额后溢写磁盘,最终合并临时文件。 - 若分区数少于200,启用Bypass模式,Task直接写入临时文件后合并,同样不会将数据保留在内存中。
4. 实际可用内存的损耗
理论内存配额需扣除JVM开销:
- Executor的JVM元空间、GC预留内存等会占用约40%的堆内存(
spark.memory.fraction=0.6意味着仅60%堆内存给Spark统一池),实际可用于计算的内存比理论值更低。
验证与优化方向
- 查看Spark UI的Executors页,确认统一内存池的实际占用,验证内存配额是否符合预期。
- 调整Shuffle参数:
- 增大
spark.shuffle.file.buffer(如设为64KB/128KB),减少刷盘次数,但无法避免溢写。 - 若Shuffle分区数较少,调高
spark.shuffle.sort.bypassMergeThreshold启用Bypass模式,降低排序开销。
- 增大
- 优化Shuffle分区数:调高
spark.sql.shuffle.partitions,减少单Task处理的数据量,降低内存压力。 - 排查数据倾斜:通过Spark UI的Tasks页查看单Task的Shuffle写大小,若存在极个别Task写入量远超均值,需针对倾斜Key做拆分、加盐处理。
内容的提问来源于stack exchange,提问作者Klun
相关产品推荐
相关产品推荐

