You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spark中禁用扫描后的谓词下推优化

问题:如何禁用Spark的Post-Scan Filter避免重复执行array_intersect

我尝试禁用Spark的一项优化。从物理执行计划和日志可见,Spark在ORC扫描(Scan orc)后添加了包含array_intersect的Post-Scan Filter,该操作在后续Project步骤中重复执行,导致作业耗时增加50%。使用.cache()可避免该下推并提速,但数据集过大(磁盘占用达100TB)无法采用。尝试配置spark.sql.optimizer.excludedRules排除PushDownPredicate无效,请问如何不使用cache禁用该扫描后过滤?


物理执行计划

== Physical Plan ==
AdaptiveSparkPlan (11)
+- Project (10)
   +- Project (9)
      +- BroadcastHashJoin Inner BuildRight (8)
         :- Project (3)
         :  +- Filter (2)
         :     +- Scan orc  (1)
         +- BroadcastExchange (7)
            +- Project (6)
               +- Filter (5)
                  +- Scan orc  (4)


(1) Scan orc 
Output [2]: [id#70, segment#71]
Batched: false
Location: InMemoryFileIndex [file:/....]
PushedFilters: [IsNotNull(id)]
ReadSchema: struct<id:string,segment:array<struct<ids:bigint>>>

(2) Filter
Input [2]: [id#70, segment#71]
Condition : ((size(array_intersect(segment#71.ids, [100,200,300,400]), true) > 0) AND isnotnull(id#70))

日志信息

23/08/03 09:06:50 INFO FileSourceStrategy: Pushed Filters: IsNotNull(id)
23/08/03 09:06:50 INFO FileSourceStrategy: Post-Scan Filters: (size(array_intersect(segment#71.ids, [100,200,300,400]), true) > 0),isnotnull(id#70)

作业代码

spark.read.orc(s"input")
  .select(
    col("id"),
    col("segment.ids").as("segment_ids"),
  )
 .join(otherSmallDf, Seq("id"), "inner")
 .select(
    someUDF(col("id")).as("userId"),
    col("segment_ids")
  )
 .withColumn("segment_ids", array_intersect(col("segment_ids"), shareableSegmentIds))
 .where(size(col("segment_ids")) > 0)
 .write.option("sep", "\t").csv("output/path")

解决方案

1. 用临时视图添加优化屏障,阻止过滤下推

通过创建临时视图,让Spark将数据读取和后续处理视为两个独立阶段,打断优化器的下推逻辑:

// 读取数据并创建临时视图
spark.read.orc(s"input")
  .select(
    col("id"),
    col("segment.ids").as("segment_ids"),
  )
 .createOrReplaceTempView("temp_input")

// 从临时视图读取后执行后续逻辑
spark.sql("SELECT id, segment_ids FROM temp_input")
 .join(otherSmallDf, Seq("id"), "inner")
 .select(
    someUDF(col("id")).as("userId"),
    col("segment_ids")
  )
 .withColumn("segment_ids", array_intersect(col("segment_ids"), shareableSegmentIds))
 .where(size(col("segment_ids")) > 0)
 .write.option("sep", "\t").csv("output/path")

2. 排除更精准的优化规则

PushDownPredicate是顶层规则,实际生成Post-Scan Filter的是PushDownFilters和数据源级别的PushDownPredicate,可尝试同时排除这两个规则(注意Spark版本差异,以下为Spark 3.x路径):

spark.sql.optimizer.excludedRules=org.apache.spark.sql.catalyst.optimizer.PushDownFilters,org.apache.spark.sql.execution.datasources.PushDownPredicate

3. 拆分读取与join,延迟嵌套字段解析

通过拆分数据读取步骤,先只读取id字段完成join,再关联原表获取嵌套字段,让过滤逻辑仅在join后执行:

import org.apache.spark.sql.types._

val customSchema = StructType(Seq(
  StructField("id", StringType),
  StructField("segment", StructType(Seq(StructField("ids", ArrayType(LongType)))))
))

// 先读取id字段完成join
val joinedIds = spark.read.schema(customSchema).orc(s"input")
  .select(col("id"))
 .join(otherSmallDf, Seq("id"), "inner")

// 关联原表获取segment字段,再执行过滤逻辑
joinedIds.join(
    spark.read.schema(customSchema).orc(s"input").select(col("id"), col("segment.ids").as("segment_ids")),
    Seq("id")
  )
 .select(
    someUDF(col("id")).as("userId"),
    col("segment_ids")
  )
 .withColumn("segment_ids", array_intersect(col("segment_ids"), shareableSegmentIds))
 .where(size(col("segment_ids")) > 0)
 .write.option("sep", "\t").csv("output/path")

内容的提问来源于stack exchange,提问作者adi rohan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 16:15:57