Apache Arrow 格式如何加速查询结果传输


已发布 2025 年 1 月 10 日
作者 Ian Cook、David Li、Matt Topol

本系列文章的第一篇旨在揭秘 Arrow 作为数据库和查询引擎的数据交换格式的用途。

“为什么这么慢?”

这是数据从业者在等待查询结果时经常思考的问题。这个问题有很多可能的答案。也许你的数据源分区不佳。也许你的 SaaS 数据仓库规模过小。也许查询优化器未能将你的 SQL 语句转换为高效的执行计划。

但令人惊讶的是,答案往往是,你正在使用低效的协议将查询结果传输到客户端。在 Mark Raasveldt 和 Hannes Mühleisen 于2017 年发表的论文中,他们观察到查询结果传输时间通常占据查询执行时间的主导地位,尤其是在结果较大时。然而,瓶颈并不在你可能预料的地方。

将查询结果从源传输到目标涉及三个步骤

  1. 在源端,将结果从其原始格式序列化为传输格式。
  2. 以传输格式通过网络传输数据。1
  3. 在目标端,将传输格式反序列化为目标格式。

在网络速度较慢的时代,传输步骤通常是瓶颈,因此几乎没有动力去加速序列化和反序列化步骤。相反,重点是减小传输数据的大小(通常使用压缩)以减少传输时间。正是在这个时代,设计了最广泛使用的数据库连接 API(ODBC 和 JDBC)和数据库客户端协议(例如 MySQL 客户端/服务器协议和 PostgreSQL 前端/后端协议)。但随着网络速度越来越快,传输时间越来越短,瓶颈已经转移到序列化和反序列化步骤。2 对于产生许多数据工程和数据分析管道特征的较大结果大小的查询来说尤其如此。

然而,今天许多查询结果继续流经传统的 API 和协议,这些 API 和协议通过强制将数据转换为低效的传输格式而增加了大量的序列化和反序列化(“ser/de”)开销。在 Tianyu Li 等人于 2021 年发表的论文中,他们展示了一个使用 ODBC 和 PostgreSQL 协议的示例,其中 99.996% 的总查询时间都花在了 ser/de 上。这可以说是极端情况,但我们在许多实际案例中观察到 90% 或更高。如今,对于数据工程和数据分析查询,强烈建议选择一种可以加速 ser/de 的传输格式。

Arrow 应运而生。

Apache Arrow 开源项目定义了一种数据格式,旨在加速(在许多情况下消除)查询结果传输中的 ser/de。自 2016 年创建以来,Arrow 格式及其周围构建的多语言工具箱已得到广泛使用,但 Arrow 如何大幅削减 ser/de 开销的技术细节仍然鲜为人知。为了帮助解决这个问题,我们概述了 Arrow 格式的五个关键属性,使之成为可能。

1. Arrow 格式是列式的。

列式(面向列)数据格式将每列的值存储在连续的内存块中。这与面向行的数据格式相反,后者将每行的值存储在连续的内存块中。

Figure 1: An illustration of row-oriented and column-oriented physical memory layouts of a table containing three rows and five columns.
图 1:包含三行五列的表的行式和列式物理内存布局示意图。

高性能分析数据库、数据仓库、查询引擎和存储系统已经融合到列式架构,因为它可以加速最常见的分析查询类型。现代列式查询系统的示例包括 Amazon Redshift、Apache DataFusion、ClickHouse、Databricks Photon Engine、DuckDB、Google BigQuery、Microsoft Azure Synapse Analytics、OpenText Analytics Database (Vertica)、Snowflake 和 Voltron Data Theseus。

同样,分析查询结果的许多目标(例如商业智能工具、数据应用程序平台、数据帧库和机器学习平台)都使用列式架构。列式商业智能工具的示例包括 Amazon QuickSight、Domo、GoodData、Power BI、Qlik Sense、Spotfire 和 Tableau。列式数据帧库的示例包括 cuDF、pandas 和 Polars。

因此,查询结果的源格式和目标格式都是列式格式的情况越来越普遍。在列式源和列式目标之间传输数据的最有效方法是使用列式传输格式。这消除了在序列化步骤期间在源端将数据从列转换为行的耗时转置,以及在反序列化步骤期间在目标端将数据从行转换为列的另一个耗时转置。

Arrow 是一种列式数据格式。Arrow 格式中数据的列式布局与许多广泛使用的列式源系统和目标系统中的数据布局相似(在许多情况下相同)。

2. Arrow 格式是自描述的且类型安全的。

在自描述数据格式中,描述数据结构的模式(列的名称和类型)和其他元数据与数据一起包含在内。自描述格式为接收系统提供了安全有效地处理数据所需的所有信息。相反,当格式不是自描述的时,接收系统必须扫描数据以推断其模式和结构(一个缓慢且容易出错的过程)或单独获取模式。

某些自描述数据格式的一个重要特性是强制执行类型安全的能力。当格式强制执行类型安全时,它可以保证数据值与其指定的类型一致,从而允许接收系统在处理数据时排除类型错误的可能性。相反,当格式不强制执行类型安全时,接收系统必须检查数据中每个值的有效性(一个计算量很大的过程)或在处理数据时处理类型错误。

从非自描述、类型不安全的格式(例如 CSV)读取数据时,所有这些扫描、推断和检查都会导致大量的反序列化开销。更糟糕的是,这种格式会导致歧义、调试问题、维护挑战和安全漏洞。

Arrow 格式是自描述的并强制执行类型安全。此外,Arrow 的类型系统与许多广泛使用的数据源和目标的类型系统相似(在许多情况下相同或超集)。这包括大多数列式数据系统和许多行式系统,例如 Apache Spark 和各种关系数据库。使用 Arrow 格式时,这些系统可以在其原生类型和相应的 Arrow 类型之间快速安全地转换数据值。

3. Arrow 格式支持零拷贝。

零拷贝操作是指将数据从一种介质传输到另一种介质而无需创建任何中间副本。当数据格式支持零拷贝操作时,这意味着其在内存中的结构与其在磁盘或网络上的结构相同。因此,例如,可以直接从网络读取数据到内存中的可用结构,而无需执行任何中间复制或转换。

Arrow 格式支持零拷贝操作。为了保存数据集,Arrow 定义了一种称为记录批的面向列的表格数据结构。Arrow 记录批可以保存在内存中,通过网络发送或存储在磁盘上。无论记录批位于哪个介质上以及哪个系统生成了它,二进制结构都保持不变。为了保存模式和其他元数据,Arrow 内部使用 FlatBuffers,这是 Google 创建的一种格式,无论它位于哪个介质上,其二进制结构都相同。

由于这些设计选择,Arrow 不仅可以作为一种传输格式,还可以作为一种内存格式和磁盘格式。这与基于文本的格式(如 JSON 和 CSV)以及序列化二进制格式(如 Protocol Buffers 和 Thrift)形成对比,后者使用专用的结构语法对数据值进行编码。要将来自这些格式的数据加载到可用的内存结构中,必须对数据进行解析和解码。这也与 Parquet 和 ORC 等二进制格式形成对比,后者使用编码和压缩来减小磁盘上数据的大小。要将来自这些格式的数据加载到可用的内存结构中,必须对其进行解压缩和解码。3

这意味着,在源系统中,如果数据以 Arrow 格式存在于内存或磁盘上,则可以直接以 Arrow 格式通过网络传输数据,而无需任何序列化操作。在目标系统中,可以直接从网络读取 Arrow 格式的数据到内存或磁盘上的 Arrow 文件中,而无需任何反序列化操作。

Arrow 格式被设计为一种用于分析操作的高效内存格式。因此,许多列式数据系统都使用 Arrow 作为其内存格式。这些系统包括 Apache DataFusion、cuDF、Dremio、InfluxDB、Polars、Velox 和 Voltron Data Theseus。当这些系统之一是传输的源或目标时,可以完全消除序列化/反序列化的开销。对于大多数其他列式数据系统,它们使用的专有内存格式与 Arrow 非常相似。使用这些系统,序列化到 Arrow 格式和从 Arrow 格式反序列化都非常快速高效。

4. Arrow 格式支持流式传输。

可流式传输的数据格式是指可以顺序处理的数据格式,一次处理一个块,无需等待完整的数据集。当数据以可流式传输的格式传输时,接收系统可以在第一个块到达后立即开始处理它。这可以通过多种方式加快数据传输速度:传输时间可以与处理时间重叠;接收系统可以更有效地使用内存;可以并行传输多个流,从而加快传输、反序列化和处理速度。

CSV 是可流式传输数据格式的一个例子,因为列名(如果包含)位于文件顶部的标题中,并且可以顺序处理文件中的行。Parquet 和 ORC 是不支持流式传输的数据格式的示例,因为处理数据所需的 schema 和其他元数据保存在文件底部的页脚中,因此必须下载整个文件(或查找文件末尾并单独下载页脚)才能开始任何处理。4

Arrow 是一种可流式传输的数据格式。数据集可以在 Arrow 中表示为一系列具有相同 schema 的记录批次。Arrow 定义了一种流式传输格式,由 schema 后跟一个或多个记录批次组成。接收 Arrow 流的系统可以按记录批次到达的顺序依次处理它们。

Figure 2: An illustration of an Arrow stream transmitting data from a table with three columns. The first record batch contains the values for the first three rows, the second record batch contains the values for the next three rows, and so on. Actual Arrow record batches might contain thousands to millions of rows.
图 2:Arrow 流从具有三列的表传输数据的示意图。第一个记录批次包含前三行的值,第二个记录批次包含接下来的三行的值,依此类推。实际的 Arrow 记录批次可能包含数千到数百万行。

5. Arrow 格式是通用的。

Arrow 已成为在内存中处理表格数据的实际标准格式。Arrow 格式是一种与语言无关的开放标准。可以使用库来处理各种语言(包括 C、C++、C#、Go、Java、JavaScript、Julia、MATLAB、Python、R、Ruby、Rust 和 Swift)中的 Arrow 数据。几乎任何主流语言开发的应用程序都可以添加对发送或接收 Arrow 格式数据的支持。数据不需要像某些数据库连接 API(包括 JDBC)那样通过特定的语言运行时。

Arrow 的通用性使其能够解决加快现实世界数据系统速度的一个基本问题:性能改进本质上受到系统瓶颈的限制。这个问题被称为阿姆达尔定律。在现实世界的数据流水线中,查询结果通常会流经多个阶段,在每个阶段都会产生序列化/反序列化的开销。例如,如果您的数据流水线有五个阶段,并且您消除了其中四个阶段的序列化/反序列化开销,则您的系统可能不会比以前更快,因为剩余一个阶段的序列化/反序列化将成为整个流水线的瓶颈。

Arrow 能够在几乎任何技术堆栈中高效运行,有助于解决这个问题。您的数据是否从基于 Scala 的分布式后端(具有 NVIDIA GPU 加速的工作器)流向基于 Jetty 的 HTTP 服务器,然后流向 Rails 驱动的特征工程应用程序,用户通过基于 Node.js 的机器学习框架与基于 Pyodide 的浏览器前端进行交互?没问题;Arrow 库可用于消除所有这些组件之间的序列化/反序列化开销。

结论

随着越来越多的商业和开源工具添加了对 Arrow 的支持,快速查询结果传输和低或无序列化/反序列化开销变得越来越普遍。如今,包括 Databricks、Dremio、Google BigQuery、InfluxDB、Snowflake 和 Voltron Data Theseus 在内的商业数据平台和查询引擎,以及包括 Apache DataFusion、Apache Doris、Apache Spark、ClickHouse 和 DuckDB 在内的开源数据库和查询引擎都可以以 Arrow 格式传输查询结果。速度提升非常显著。

在接收端,数据从业者可以通过使用基于 Arrow 的工具和 Arrow 库、接口和协议来最大限度地提高速度。到 2025 年,随着越来越多的项目和供应商实施对ADBC标准的支持,我们预计能够以 Arrow 格式接收查询结果的工具数量将会加速增长。

请继续关注本系列的后续文章,这些文章将比较 Arrow 格式与其他数据格式,并描述客户端可以用来以 Arrow 格式获取结果的协议和 API。


  1. 传输格式也称为线路格式或序列化格式。 

  2. 从 20 世纪 90 年代至今,网络性能的提升速度超过了 CPU 性能的提升速度。例如,在 20 世纪 90 年代后期,主流台式机 CPU 的运算能力约为 1 GFLOPS,典型的 WAN 连接速度为 56 Kb/s。如今,主流台式机 CPU 的运算能力约为 100 GFLOPS,WAN 连接速度约为 1 Gb/s。因此,CPU 性能提高了约 100 倍,而网络速度提高了约 10,000 倍。 

  3. 这并不意味着 Arrow 在其他应用(如存档存储)中比 Parquet 或 ORC 更快。本系列的后续文章将更详细地比较 Arrow 格式与这些格式和其他格式,并描述它们如何经常相互补充。 

  4. 这并不意味着 CSV 传输结果的速度会比 Parquet 或 ORC 快。比较 CSV 与 Parquet 或 ORC 的传输性能时,此处描述的其他属性通常会比这一个属性更重要。