Spark作业可溢写执行与存储内存仍内存溢出的原因探究
Spark执行内存可溢写仍出现OOM的核心原因
溢写触发滞后或阈值限制
Spark执行内存的溢写逻辑要等到内存使用达到设定阈值(默认是执行内存区域的75%)才会启动,但如果中间数据生成速度远超溢写的磁盘IO速度——比如宽表join、超大分组聚合瞬间产生海量数据——内存会在溢写完成前就被耗尽,直接触发OOM。另外,堆内内存GC停顿会拖慢溢写逻辑的执行节奏,进一步加剧内存耗尽的速度。部分场景数据无法溢写
不是所有执行数据都能被写到磁盘:- Shuffle聚合阶段的哈希表,在构建过程中如果键值对爆发式增长,还没触发溢写阈值就已经把执行内存撑爆;
collect、cartesian这类算子会强制把数据加载到内存里,根本不会触发溢写逻辑——比如collect会把分布式数据全拉到Driver端,数据量超了直接OOM;- 自定义UDF或第三方库的对象没实现序列化,没法被溢写到磁盘,这部分数据会一直占着内存直到耗尽。
存储内存抢占受限制
虽然执行内存和存储内存可以互相抢占,但有硬限制:- 如果存储内存里缓存了用
persist(StorageLevel.MEMORY_ONLY)标记的不可驱逐数据,执行内存没法抢占这部分空间,只能用剩下的执行内存,要是执行数据量超了剩余内存,就会OOM; - 抢占存储内存时需要清理缓存数据,这个过程中的GC开销会导致内存暂时无法释放,新的执行数据持续进来就会直接溢出。
- 如果存储内存里缓存了用
Driver端内存拖后腿
很多人会忽略Driver端的内存瓶颈:- 执行
count、take、show这类动作时,结果会被拉到Driver内存里,结果集过大的话,哪怕Executor端有溢写机制,Driver照样OOM; - 广播变量太大(比如广播几十万条的字典)、任务调度产生的临时对象过多,都会直接占满Driver内存引发溢出。
- 执行
内存计算的隐性开销
执行过程中会产生很多没被纳入溢写统计的临时对象——比如序列化/反序列化的中间对象、GC没回收的存活对象,还有Spark包装数据用的GenericRowWithSchema这类对象,它们的内存占用往往远超原始数据大小,实际内存使用会远超预期,最后触发OOM。
内容的提问来源于stack exchange,提问作者Abhijeet Sachdev
相关产品推荐
相关产品推荐

