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 Compute 处理所有数据都在内存中的情况。
Arrow 数据集#
Arrow 数据集库提供了一些用于发现、扫描和写入文件集合的基本例程。数据集模块依赖于 Acero。扫描和写入数据集都使用 Acero。扫描节点和写入节点是数据集模块的一部分。这有助于将文件格式和文件系统的复杂性排除在核心 Acero 逻辑之外。
Substrait#
Substrait 是一个建立查询计划标准的项目。Acero 执行查询计划并生成数据。这使得 Acero 成为 Substrait 的使用者。有关 Substrait 功能的更多详细信息,请参阅使用 Acero 与 Substrait。
Datafusion/DuckDb/Velox/等#
许多列式数据引擎正在兴起。我们认为这是一件好事,并鼓励像 Substrait 这样的项目帮助根据需要在引擎之间切换。我们通常不鼓励进行比较基准测试,因为它们几乎不可避免地会受工作负载驱动,并且很少能捕捉到苹果与苹果的比较。有关每个优缺点的讨论超出了本指南的范围。
与 Arrow C++ 的关系#
Acero 模块是 Arrow C++ 实现的一部分。它作为单独的模块构建,但它依赖于核心 Arrow 模块,并且不独立存在。Acero 使用并扩展了核心 Arrow 模块和 Arrow 计算内核的功能。
核心 Arrow 库提供了用于缓冲区和数组的容器,这些容器根据 Arrow 列式格式布局。除了少数例外情况外,核心 Arrow 库不会检查或修改缓冲区的内容。例如,将字符串数组从小写字符串转换为大写字符串将不是核心 Arrow 库的一部分,因为这需要检查数组的内容。
计算模块扩展了核心库,并提供了分析和转换数据的功能。计算模块的所有功能都通过函数注册表公开。Arrow“函数”接受零个或多个数组、批次或表,并生成数组、批次或表。此外,函数调用可以与字段引用和文字结合起来,形成一个表达式(函数调用的树),计算模块可以评估该表达式。例如,计算x + (y * 3)
,给定一个包含列x
和y
的表。
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 有几个关键区别
ExecBatch
没有架构。这是因为ExecBatch
假定是批次流的一部分,并且该流假定具有一致的架构。因此,ExecBatch
的架构通常存储在 ExecNode 中。ExecBatch
中的列要么是Array
,要么是Scalar
。当列为Scalar
时,这意味着该列对于批次中的每一行都有一个值。ExecBatch
还具有一个长度属性,该属性描述了批次中有多少行。因此,查看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 是一个内部概念。创建计划的用户应该使用 Declaration 对象。用于使用和执行计划的 API 应该抽象掉底层计划的细节,而不公开对象本身。
声明#
Declaration 是 ExecNode 的蓝图。Declaration 可以组合成一个图,形成 ExecPlan 的蓝图。Declaration 描述了需要完成的计算,但实际上并不负责执行计算。这样,Declaration 就类似于表达式。预计 Declaration 将需要在各种查询表示(例如 Substrait)之间进行转换。Declaration 对象是公共 API,结合 DeclarationToXyz 方法,是 Acero 当前的公共 API。