Spark Datafusion Comet 向量化Rule--CometScanRule分析

背景

Apache Datafusion Comet 是苹果公司开源的加速Spark运行的向量化项目。

本项目采用了 Spark插件化 + Brotobuf + Arrow + DataFusion 架构形式

其中

  • Spark插件是 利用 SparkPlugin 插件,其中分为 DriverPlugin 和 ExecutorPlugin ,这两个插件在driver和 Executor启动的时候就会调用
  • Brotobuf 是用来序列化 spark对应的表达式以及计划,用来传递给 native 引擎去执行,利用了 体积小,速度快的特性
  • Arrow 是用来 spark 和 native 引擎进行高效的数据交换(native执行的结果或者spark执行的数据结果),主要在JNI中利用Arrow IPC 列式存储以及零拷贝等特点进行进程间数据交换
  • DataFusion 主要是利用Rust native以及Arrow内存格式实现的向量化执行引擎,Spark中主要offload对应的算子到该引擎中去执行

本文基于 datafusion comet 截止到2026年1月13号的main分支的最新代码(对应的commit为 eef5f28a0727d9aef043fa2b87d6747ff68b827a)

主要分析 CometSparkSessionExtensions 中的向量化规则转换

CometScanRule分析

上代码:

复制代码
class CometSparkSessionExtensions
    extends (SparkSessionExtensions => Unit)
    with Logging
    with ShimCometSparkSessionExtensions {
  override def apply(extensions: SparkSessionExtensions): Unit = {
    extensions.injectColumnar { session => CometScanColumnar(session) }
    extensions.injectColumnar { session => CometExecColumnar(session) }
    extensions.injectQueryStagePrepRule { session => CometScanRule(session) }
    extensions.injectQueryStagePrepRule { session => CometExecRule(session) }
  }

  case class CometScanColumnar(session: SparkSession) extends ColumnarRule {
    override def preColumnarTransitions: Rule[SparkPlan] = CometScanRule(session)
  }

  case class CometExecColumnar(session: SparkSession) extends ColumnarRule {
    override def preColumnarTransitions: Rule[SparkPlan] = CometExecRule(session)

    override def postColumnarTransitions: Rule[SparkPlan] =
      EliminateRedundantTransitions(session)
  }
}

这里的 CometScanColumnar 最终也是调用 CometScanRule 方法,所以这里只分析 CometScanRule 这个规则:

复制代码
case class CometScanRule(session: SparkSession) extends Rule[SparkPlan] with CometTypeShim {

  import CometScanRule._

  private lazy val showTransformations = CometConf.COMET_EXPLAIN_TRANSFORMATIONS.get()

  override def apply(plan: SparkPlan): SparkPlan = {
    val newPlan = _apply(plan)
    if (showTransformations && !newPlan.fastEquals(plan)) {
      logInfo(s"""
           |=== Applying Rule $ruleName ===
           |${sideBySide(plan.treeString, newPlan.treeString).mkString("\n")}
           |""".stripMargin)
    }
    newPlan
  }

这里有个spark.comet.explain.rules配置(默认是false),用来表明经过这个规则转换后,计划的前后变化。

对于_apply方法:

复制代码
  private def _apply(plan: SparkPlan): SparkPlan = {
    if (!isCometLoaded(conf)) return plan

    def isSupportedScanNode(plan: SparkPlan): Boolean = plan match {
      case _: FileSourceScanExec => true
      case _: BatchScanExec => true
      case _ => false
    }

    def hasMetadataCol(plan: SparkPlan): Boolean = {
      plan.expressions.exists(_.exists {
        case a: Attribute =>
          a.isMetadataCol
        case _ => false
      })
    }

    def transformScan(plan: SparkPlan): SparkPlan = plan match {
      case scan if !isCometScanEnabled(conf) =>
        withInfo(scan, "Comet Scan is not enabled")

      case scan if hasMetadataCol(scan) =>
        withInfo(scan, "Metadata column is not supported")

      // data source V1
      case scanExec: FileSourceScanExec =>
        transformV1Scan(scanExec)

      // data source V2
      case scanExec: BatchScanExec =>
        if (isIcebergMetadataTable(scanExec)) {
          withInfo(scanExec, "Iceberg Metadata tables are not supported")
        } else {
          transformV2Scan(scanExec)
        }
    }

    plan.transform {
      case scan if isSupportedScanNode(scan) => transformScan(scan)
    }
  }
  • 首先 isCometLoaded方法,这里会有个lazy val isBigEndian: Boolean = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN)

    判断平台是不是大端的判断,如果是大端的话,就直接退出。

    这里为什么是大端就直接退出了呢,是因为一般来说 如果涉及到网路传输的时候,会涉及到多字节整数的存储问题,如果网络传输方和接收方在大小端不一致的时候

    会导致传输和接收的数据不一致的问题。

    而在Datafusion Comet这个项目中使用小端进行数据的交互,主要是在Shuffle数据文件的时候,具体的可以见 NativeBatchDecoderIterator :

    复制代码
      private val longBuf = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN)

    这里会以小端的形式进行存储,所以平台测如果是大端的话就直接退出了

  • 保证 spark.comet.enabled(默认为true)True

  • 还有保证rust 动态库被成功加载
    NativeBase.isLoaded 就是来判断对应的动态库是不是已经被成功加载,如果没成功加载,则直接报错

    动态库的加载 在 NativeBase的 静态代码块中

  • transformScan主要用来对scan的计划进行转换,分Datasource V1和V2

    • datasource V1 (FileSourceScanExec)
      这里的代码比较多,直接说重点
      • 只有relation为HadoopFsRelation类型的才继续
      • 如果存在动态分区裁剪的话且(spark.comet.dppFallback.enabled为true(默认是true)),就直接返回Spark 自带的FileSourceScanExec,
      • 对于 spark.comet.scan.impl 为auto的(默认为auto), 如果不是不是本地文件以及S3文件系统,则设置为 SCAN_NATIVE_COMET
        最终组成CometScanExec(HadoopFsRelation(fileFormat = new CometParquetFileFormat))
    • datasource V2(BatchScanExec)
      • 只有Scan为ParquetScan才继续
      • 并且满足一定的数据类型才可以
      • 如果scan中有聚合下推,则返回Spark BatchScanExec额scan,否则最终会返回CometBatchScanExec
相关推荐
prince051 天前
用户积分系统怎么设计
java·大数据·数据库
什么时候才能变强1 天前
竞态条件场景、测试思路讲解
大数据
QYR_111 天前
香叶醇行业深度解析:香精香料领域核心原料的发展潜力与挑战
大数据·人工智能·物联网
港股研究社1 天前
腾讯音乐的多元增长新路径:音乐IP经济
大数据·人工智能·tcp/ip
GIOTTO情1 天前
技术解析:Infoseek基于AI重构媒介投放全链路,适配2026年奥斯卡高端投放场景
大数据·人工智能
Data-Miner1 天前
46页精品PPT | 数据治理大数据平台资源规划与建设解决方案
大数据
信道者1 天前
乌克兰开放战场数据宝库:AI无人机迎来“实战级”进化
大数据·人工智能·无人机
margu_1681 天前
【Elasticsearch】es7.2单节点集群内索引重组迁移
大数据·elasticsearch
武子康1 天前
大数据-251 离线数仓 - Airflow 安装部署避坑指南:1.10.11 与 2.x 命令差异、MySQL 配置与错误排查
大数据·后端·apache hive