Acero 概览#
本页面概述了 Acero 的基本概念,并帮助区分 Acero 与 Arrow 代码库中的其他模块。它面向用户、开发者、潜在贡献者以及想要扩展 Acero(无论是用于研究还是商业用途)的人员。本页面假设读者已经熟悉 Arrow 的核心概念。本页面不要求读者具备任何关系代数的知识。
什么是 Acero?#
Acero 是一个 C++ 库,可用于分析大型(可能是无限的)数据流。Acero 允许将计算表示为“执行计划”(ExecPlan
)。执行计划接收零个或多个输入数据流,并发出单个输出数据流。该计划描述了数据在传递过程中将如何转换。例如,一个计划可能:
使用公共列合并两个数据流
通过根据现有列计算表达式来创建其他列
通过以分区布局将数据流写入磁盘来使用数据流
Acero 不是…#
数据科学家的库#
Acero 不是供数据科学家直接使用的。预计最终用户通常会使用某种前端,例如 Pandas、Ibis 或 SQL。Acero 的 API 主要关注功能和可用算法。然而,这些用户可能有兴趣了解更多关于 Acero 的工作原理,以便他们能够更好地理解其库的后台处理是如何运作的。
数据库#
数据库(或 DBMS)通常是一个更广泛的应用程序,并且通常打包成一个独立的服务。Acero 可以是数据库中的一个组件(大多数数据库都有某种执行引擎),也可以是其他几乎不像数据库的数据处理应用程序中的一个组件。Acero 不关心用户管理、外部通信、隔离性、持久性或一致性。此外,Acero 主要关注读取路径,并且写入实用程序缺乏任何形式的事务支持。
优化器#
Acero 没有 SQL 解析器,也没有查询规划器,也没有任何形式的优化器。Acero 期望获得关于如何操作数据的非常详细和低级别的指令,然后它将完全按照描述执行该操作。
创建最佳执行计划非常困难。小的细节可能会对性能产生重大影响。我们认为优化器很重要,但我们相信它应该独立于 Acero 实现,最好是通过 Substrait 等标准以可组合的方式实现,以便任何后端都可以利用它。
分布式#
Acero 不提供分布式执行。但是,Acero 的目标是可供分布式查询执行引擎使用。换句话说,Acero 不会配置和协调工作节点,但它确实希望被用作工作节点。有时,这种区别有点模糊。例如,Acero 源可能是一个能够执行过滤或其他高级分析的智能存储设备。有人可能会认为这是一个分布式计划。关键区别在于 Acero 没有将逻辑计划转换为分布式执行计划的能力。该步骤需要在其他地方完成。
Acero 与…#
Arrow 计算#
这在与 Arrow C++ 的关系中有更详细的描述,但主要区别在于 Acero 处理数据流,而 Arrow 计算处理所有数据都在内存中的情况。
Arrow 数据集#
Arrow 数据集库提供了一些用于发现、扫描和写入文件集合的基本例程。数据集模块依赖于 Acero。扫描和写入数据集都使用 Acero。扫描节点和写入节点是数据集模块的一部分。这有助于将文件格式和文件系统的复杂性排除在 Acero 核心逻辑之外。
Substrait#
Substrait 是一个为查询计划建立标准的项目。Acero 执行查询计划并生成数据。这使得 Acero 成为 Substrait 的使用者。将 Acero 与 Substrait 结合使用中更详细地介绍了 Substrait 的功能。
Datafusion / DuckDb / Velox / 等#
目前正在涌现许多列式数据引擎。我们认为这是一件好事,并鼓励 Substrait 等项目,以帮助根据需要在引擎之间切换。我们通常不鼓励进行比较基准测试,因为它们几乎不可避免地会受到工作负载的驱动,并且很少能够捕捉到苹果与苹果之间的比较。讨论每种方法的优缺点超出了本指南的范围。
与 Arrow C++ 的关系#
Acero 模块是 Arrow C++ 实现的一部分。它被构建为一个单独的模块,但它依赖于 Arrow 核心模块,并且不能独立存在。Acero 使用并扩展了 Arrow 核心模块和 Arrow 计算内核的功能。
Arrow 核心库提供缓冲区和数组的容器,这些容器按照 Arrow 列式格式进行布局。除少数例外情况外,Arrow 核心库不会检查或修改缓冲区的内容。例如,将字符串数组从小写字符串转换为大写字符串不属于 Arrow 核心库的一部分,因为这需要检查数组的内容。
计算模块扩展了核心库,并提供了分析和转换数据的函数。计算模块的功能都通过函数注册表公开。Arrow“函数”接受零个或多个数组、批次或表格,并生成一个数组、批次或表格。此外,函数调用可以与字段引用和字面量组合起来形成表达式(函数调用的树),计算模块可以对其进行求值。例如,给定一个包含列 x
和 y
的表格,计算 x + (y * 3)
。
Acero 通过添加针对数据流的计算操作来扩展这些功能。例如,一个项目节点可以对数据流批次应用计算表达式。这将创建一个新的数据流批次,并将表达式的结果添加为新列。这些节点可以组合成一个图来形成更复杂的执行计划。这与将函数组合成树以形成复杂表达式的方式非常相似。
注意
Acero 不使用核心 Arrow 库中的 arrow::Table
或 arrow::ChunkedArray
容器。这是因为 Acero 对批处理流进行操作,因此不需要多批处理数据容器。这有助于降低 Acero 的复杂性,并避免因表中列具有不同块大小而引起的棘手情况。Acero 通常会使用 arrow::Datum
,它是核心模块中的一个变体,可以容纳许多不同的类型。在 Acero 中,一个 Datum 将始终包含一个 arrow::Array
或一个 arrow::Scalar
。
核心概念#
执行节点(ExecNode)#
Acero 中最基本的概念是 ExecNode。一个 ExecNode 有零个或多个输入,以及零个或一个输出。如果一个 ExecNode 没有输入,我们称它为源节点;如果一个 ExecNode 没有输出,我们称它为汇聚节点。存在许多不同类型的节点,每个节点都以不同的方式转换其输入。例如:
扫描节点是一个源节点,它从文件中读取数据。
聚合节点累积数据批次以计算汇总统计信息。
过滤节点根据过滤表达式从数据中删除行。
表汇聚节点将数据累积到一个表中。
注意
可用计算模块的完整列表包含在用户指南中。
执行批次(ExecBatch)#
数据批次由 ExecBatch 类表示。ExecBatch 是一个与 RecordBatch 非常相似的二维结构。它可以有零个或多个列,并且所有列必须具有相同的长度。ExecBatch 与 RecordBatch 之间有一些关键区别:
记录批次和执行批次都对数组和缓冲区拥有强所有权#
ExecBatch
没有模式。这是因为假定ExecBatch
是批处理流的一部分,并且假定该流具有一致的模式。因此,ExecBatch
的模式通常存储在 ExecNode 中。ExecBatch
中的列可以是Array
或Scalar
。当一列是Scalar
时,这意味着该列对于批次中的每一行都有一个单一值。ExecBatch
还有一个 length 属性,用于描述批次中有多少行。因此,查看Scalar
的另一种方法是将其视为具有length
个元素的常量数组。ExecBatch
包含执行计划使用的其他信息。例如,可以使用index
来描述批次在有序流中的位置。我们预计ExecBatch
还会发展为包含其他字段,例如选择向量。
使用数组和标量的不同组合,可以使用四种不同的方式来表示给定的数据批次。所有四个执行批次都应被视为语义等效的。#
从记录批次转换为执行批次始终是零拷贝的。RecordBatch 和 ExecBatch 都引用完全相同的底层数组。只有当执行批次中没有标量时,从执行批次转换为记录批次才是零拷贝的。
注意
Acero 和计算模块都有批次和数组的“轻量级”版本。在计算模块中,它们被称为 BatchSpan
、ArraySpan
和 BufferSpan
。在 Acero 中,这个概念被称为 KeyColumnArray
。这些类型是同时开发的,并且具有相同的用途。它们旨在提供一个可以完全在堆栈上分配的数组容器(前提是数据类型是非嵌套的),以避免堆分配开销。理想情况下,这两个概念将来会合并。
执行计划(ExecPlan)#
ExecPlan 表示 ExecNode 对象图。有效的 ExecPlan 必须始终至少有一个源节点,但它在技术上不需要有汇聚节点。ExecPlan 包含所有节点共享的资源,并具有控制节点启动和停止执行的实用函数。ExecPlan 和 ExecNode 都与单个执行的生命周期相关联。它们具有状态,并且预计不可重新启动。
警告
Acero 中的结构(包括 ExecBatch
)仍处于实验阶段。ExecBatch
类不应在 Acero 之外使用。相反,应将 ExecBatch
转换为更标准的结构,例如 RecordBatch
。
同样,ExecPlan 是一个内部概念。创建计划的用户应该使用声明对象。用于使用和执行计划的 API 应该抽象化底层计划的细节,而不是公开对象本身。
声明(Declaration)#
声明是 ExecNode 的蓝图。声明可以组合成一个图来形成 ExecPlan 的蓝图。声明描述了需要完成的计算,但实际上并不负责执行计算。在这种情况下,声明类似于表达式。预计声明需要与各种查询表示形式(例如 Substrait)相互转换。声明对象是公共 API,与 DeclarationToXyz 方法一起,是 Acero 当前的公共 API。
声明是一个蓝图,用于实例化执行计划实例#