Spark Paimon 中为什么我指定的分区没有下推

背景

最近在使用 Paimon 的时候遇到了一件很有意思的事情,写的 SQL 居然读取的数据不下推,明明是分区表,但是却全量扫描了。

目前使用的版本信息如下:

Spark 3.5.0

Paimon 0.6.0

paimon的建表语句如下:

复制代码
CREATE TABLE `table_demo`(
  `user_id` string COMMENT 'from deserializer' 
  )
PARTITIONED BY ( 
  `dt` string COMMENT '日期, yyyyMMdd', 
  `hour` string COMMENT '小时, HH')
ROW FORMAT SERDE 
  'org.apache.paimon.hive.PaimonSerDe' 
STORED BY 
  'org.apache.paimon.hive.PaimonStorageHandler' 
WITH SERDEPROPERTIES ( 
  'serialization.format'='1')
LOCATION
  'xxxx'
TBLPROPERTIES (
  'bucket'='50', 
  'bucketing_version'='2', 
  'bukect-key'='user_id', 
  'file.format'='parquet', 
  'merge-engine'='partial-update', 
  'partial-update.ignore-delete'='true', 
  'primary-key'='user_id', 
  'transient_lastDdlTime'='1701679855', 
  'write-only'='false')

查询的SQL如下:

复制代码
select * from 
table_demo
where dt =20231212
and hour =10
limit 100;

注意我们这里写的dt是整数类型,而表中定义的是字符串类型

结论及解决方法

结论

具体的原因是Spark DSv2中的规则 V2ScanRelationPushDown.pushDownFilters 对于 Cast类型转换表达式不会传递到DataSource端,所以只会在读取完Source转换进行过滤,

这种情况下,对于文件的读取IO会增大,但是对于shuffle等操作是不会有性能的影响的。

解决方法

对于分区字段来说,我们在写SQL对分区字段进行过滤的时候,保持和分区字段类型一致

分析

错误写法分析

针对于错误的写法,也就是导致读取全量数据的写法,我们分析一下,首先是类型转换阶段,在Spark中,对于类型不匹配的问题,spark会用规则进行转换,具体的规则是
CombinedTypeCoercionRule,

在日志中可以看到:

复制代码
=== Applying Rule org.apache.spark.sql.catalyst.analysis.TypeCoercionBase$CombinedTypeCoercionRule ===
 'GlobalLimit 100                                                                                   'GlobalLimit 100
 +- 'LocalLimit 100                                                                                     +- 'LocalLimit 100
    +- 'Project [*]                                                                                        +- 'Project [*]
!      +- 'Filter ((dt#520 = 20231212) AND (hour#521 = 10))                                                   +- Filter ((cast(dt#520 as int) = 20231212) AND (cast(hour#521 as int) = 10))
          +- SubqueryAlias spark_catalog.default.table_demo                                                      +- SubqueryAlias spark_catalog.default.table_demo
             +- RelationV2[user_id#497, dt#520, hour#521] spark_catalog.default.table_demo                          +- RelationV2[user_id#497,dt#520, hour#521] spark_catalog.default.table_demo

通过以上规则我们可以看到 过滤条件(dt#520 = 20231212) AND (hour#521 = 10) 转换为了 (cast(dt#520 as int) = 20231212) AND (cast(hour#521 as int) = 10)

接着再经过以下规则:V2ScanRelationPushDown的洗礼,我们可以看到如下日志:

复制代码
12-13 13:52:58 763  INFO (org.apache.spark.sql.execution.datasources.v2.V2ScanRelationPushDown:60) - 
Pushing operators to table_demo
Pushed Filters: IsNotNull(dt), IsNotNull(hour)
Post-Scan Filters: (cast(dt#520 as int) = 20231212),(cast(hour#521 as int) = 10)
12-13 13:52:58 723  INFO (org.apache.paimon.spark.PaimonScanBuilder:62) - pushFilter log: IsNotNull(dt),IsNotNull(hour)
12-13 13:52:58 823  INFO (org.apache.spark.sql.execution.datasources.v2.V2ScanRelationPushDown:60) - 
Output: user_id#497, dt#520, hour#521
         
12-13 13:52:58 837  INFO (org.apache.spark.sql.catalyst.rules.PlanChangeLogger:60) - 
=== Applying Rule org.apache.spark.sql.execution.datasources.v2.V2ScanRelationPushDown ===
 InsertIntoHadoopFsRelationCommand ], Overwrite, [user_id,  dt, hour]                                                                        InsertIntoHadoopFsRelationCommand xxx, false, Parquet, [path=xxx], Overwrite, [user_id, dt, hour] 
 +- WriteFiles                                                                                                                                    +- WriteFiles
    +- Repartition 1, true                                                                                                                           +- Repartition 1, true
       +- GlobalLimit 100                                                                                                                               +- GlobalLimit 100
          +- LocalLimit 100                                                                                                                                +- LocalLimit 100
!            +- Filter ((isnotnull(dt#520) AND isnotnull(hour#521)) AND ((cast(dt#520 as int) = 20231212) AND (cast(hour#521 as int) = 10)))                   +- Filter ((cast(dt#520 as int) = 20231212) AND (cast(hour#521 as int) = 10))
!               +- RelationV2[user_id#497, dt#520, hour#521] spark_catalog.default.table_demo table_demo                                                                     +- RelationV2[user_id#497, dt#520, hour#521] spark_catalog.default.table_demo   table_demo

这里只有过滤条件 isnotnull(dt#520) AND isnotnull(hour#521) 被下推到了 DataSource。

从现象来看,确实分区的过滤条件没有推到DataSource端, 我们来分析一下该规则的数据流:

复制代码
V2ScanRelationPushDown.pushDownFilters
       ||
       \/
PushDownUtils.pushFilters
       ||
       \/
DataSourceStrategy.translateFilterWithMappin
       ||
       \/
translateLeafNodeFilter

具体到translateLeafNodeFilter 方法:

复制代码
  private def translateLeafNodeFilter(
      predicate: Expression,
      pushableColumn: PushableColumnBase): Option[Filter] = predicate match {
    case expressions.EqualTo(pushableColumn(name), Literal(v, t)) =>
      Some(sources.EqualTo(name, convertToScala(v, t)))
    case expressions.EqualTo(Literal(v, t), pushableColumn(name)) =>
      Some(sources.EqualTo(name, convertToScala(v, t)))
    ...
    case _ => None

这里没有对Cast表达式进行处理,所以说最后返回的就是不能下推的处理,而 Paimon datasouce那边,具体的类为PaimonBaseScanBuilder

复制代码
  override def pushFilters(filters: Array[Filter]): Array[Filter] = {

这里传进来的filters实参 就不存在 (cast(dt#520 as int) = 20231212) AND (cast(hour#521 as int) = 10) 这个过滤条件,所以就不会下推到Paimon中去

其实不仅仅是对于Paimon Source, 其他的source也会有这个问题。

正确学法分析

正确的SQL如下:

复制代码
select * from 
table_demo
where dt ='20231212'
and hour ='10'
limit 100;

运行如上SQL,我们可以看到如下日志:

复制代码
12-14 14:22:42 328  INFO (org.apache.paimon.spark.PaimonScanBuilder:62) - pushFilter log: IsNotNull(dt),IsNotNull(hour),EqualTo(dt,20231212),EqualTo(hour,10)
12-14 14:22:42 405  INFO (org.apache.spark.sql.execution.datasources.v2.V2ScanRelationPushDown:60) - 
Pushing operators to table_demo
Pushed Filters: IsNotNull(dt), IsNotNull(hour), EqualTo(dt,20231212), EqualTo(hour,10)
Post-Scan Filters: 

=== Applying Rule org.apache.spark.sql.execution.datasources.v2.V2ScanRelationPushDown ===
 InsertIntoHadoopFsRelationCommand xxx, false, Parquet, [path=xxx], Overwrite, [user_id, dt, hour]                       InsertIntoHadoopFsRelationCommand xxx, false, Parquet, [path=xxx], Overwrite, [user_id, dt, hour]
 +- WriteFiles                                                                                                            +- WriteFiles
    +- Repartition 1, true                                                                                                   +- Repartition 1, true
       +- GlobalLimit 100                                                                                                       +- GlobalLimit 100
          +- LocalLimit 100                                                                                                        +- LocalLimit 100
!            +- Filter ((isnotnull(dt#1330) AND isnotnull(hour#1331)) AND ((dt#1330 = 20231212) AND (hour#1331 = 10)))               +- RelationV2[user_id#1307,  dt#1330, hour#1331] table_demo
!               +- RelationV2[user_id#1307,  dt#1330, hour#1331] spark_catalog.ad_dwd.table_demo table_demo                           

可以看到经过了规则转换 所有的过滤条件都下推到了DataSource了,但是具体的下推还得在DataSource进一步处理才能保证真正的下推

相关推荐
武子康14 小时前
大数据-237 离线数仓 - Hive 广告业务实战:ODS→DWD 事件解析、广告明细与转化分析落地
大数据·后端·apache hive
大大大大晴天16 小时前
Flink生产问题排障-Kryo serializer scala extensions are not available
大数据·flink
武子康3 天前
大数据-236 离线数仓 - 会员指标验证、DataX 导出与广告业务 ODS/DWD/ADS 全流程
大数据·后端·apache hive
肌肉娃子3 天前
20260227.spark.Spark 性能刺客:千万别在 for 循环里写 withColumn
spark
武子康4 天前
大数据-235 离线数仓 - 实战:Flume+HDFS+Hive 搭建 ODS/DWD/DWS/ADS 会员分析链路
大数据·后端·apache hive
DianSan_ERP4 天前
电商API接口全链路监控:构建坚不可摧的线上运维防线
大数据·运维·网络·人工智能·git·servlet
够快云库4 天前
能源行业非结构化数据治理实战:从数据沼泽到智能资产
大数据·人工智能·机器学习·企业文件安全
AI周红伟4 天前
周红伟:智能体全栈构建实操:OpenClaw部署+Agent Skills+Seedance+RAG从入门到实战
大数据·人工智能·大模型·智能体
B站计算机毕业设计超人4 天前
计算机毕业设计Django+Vue.js高考推荐系统 高考可视化 大数据毕业设计(源码+LW文档+PPT+详细讲解)
大数据·vue.js·hadoop·django·毕业设计·课程设计·推荐算法
计算机程序猿学长4 天前
大数据毕业设计-基于django的音乐网站数据分析管理系统的设计与实现(源码+LW+部署文档+全bao+远程调试+代码讲解等)
大数据·django·课程设计