Spark任务运行时出现OOM异常:physicalPlanDescription过大
physicalPlanDescription过大导致的OOM问题 我之前踩过这个坑!Spark任务因为physicalPlanDescription内容暴增触发OOM,堆栈正好指向org.apache.spark.sql.execution.QueryExecution类的completeString方法——这个方法会把从解析逻辑计划到物理计划的所有细节全拼进一个字符串里,一旦你的查询涉及复杂的物理计划(比如几十上百个join、嵌套子查询,或者字段极多的宽表),这个字符串能直接把堆内存撑爆。
先搞清楚触发场景
这个completeString方法通常在两种情况下被调用:
- 日志级别设为DEBUG时,Spark会自动打印完整执行计划详情;
- 代码里主动调用了
df.explain(true)(带详细统计的执行计划)、queryExecution.toString()这类会生成全量执行计划字符串的方法; spark.sql.debug.maxToStringFields参数值过高,导致每个执行计划节点输出过多字段。
针对性解决方案
降低日志级别,避免打印全量执行计划
如果你的日志配置把org.apache.spark.sql.execution或相关类的级别设成了DEBUG,赶紧改成INFO或WARN。DEBUG级别的日志会频繁触发completeString生成超大字符串,这是大查询场景下OOM的重灾区。可以在Spark配置里添加:spark.driver.extraJavaOptions=-Dlog4j.logger.org.apache.spark.sql.execution=INFO或者直接修改log4j配置文件中对应包的日志级别。
限制执行计划的字段输出数量
Spark的spark.sql.debug.maxToStringFields参数默认是200,如果你处理的表有几百上千个字段,这个参数会让执行计划字符串膨胀到惊人的程度。把它调小到合理值,比如50:spark.conf.set("spark.sql.debug.maxToStringFields", "50")这会大幅精简生成的执行计划字符串,减少内存占用。
避免主动生成全量执行计划字符串
检查代码里有没有调用df.explain(true)(带统计信息的详细执行计划)或者df.queryExecution.toString()这类方法。如果只是需要查看执行计划,改成df.explain()(默认只输出物理计划,不带额外统计),或者只获取执行计划的部分内容,而非全量生成字符串。临时应急:增加堆内存
如果上面的配置调整没法立刻落地,可以临时给Driver或Executor增加堆内存,比如:spark.driver.memory=8g spark.executor.memory=16g注意这只是治标,核心还是要从减少执行计划字符串的生成和大小入手。
排查小技巧
可以用jmap或者VisualVM工具查看堆内存中的大对象,确认是不是String类型的执行计划占了大部分内存。如果是的话,就可以100%确定是这个问题导致的OOM,再针对性调整配置即可。
内容的提问来源于stack exchange,提问作者igreenfield

