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


已发布 2025年1月10日
作者 Ian Cook, David Li, Matt Topol
翻译 日本語

这是系列文章的第一篇,旨在揭开使用 Arrow 作为数据库和查询引擎的数据交换格式的神秘面纱。

本系列文章

  1. Apache Arrow 格式如何加速查询结果传输
  2. 数据渴望自由:使用 Apache Arrow 进行快速数据交换

“为什么这么久?”

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

但令人惊讶的是,答案往往是您正在使用效率低下的协议将查询结果传输到客户端。在2017 年的一篇论文中,Mark Raasveldt 和 Hannes Mühleisen 观察到,查询结果传输时间通常会超过查询执行时间,尤其是对于较大的结果。然而,瓶颈并不在你可能期望的地方。

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

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

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

然而,今天许多查询结果仍然通过遗留 API 和协议传输,这些 API 和协议通过迫使数据进入低效的传输格式,增加了大量的序列化和反序列化(“ser/de”)开销。在2021 年的一篇论文中,Tianyu Li 等人提出了一个使用 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 columns and five rows.
图 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。当其中一个系统是传输的源或目标时,可以完全消除 ser/de 开销。对于大多数其他列式数据系统,它们使用的专有内存格式与 Arrow 非常相似。对于这些系统,序列化为 Arrow 和从 Arrow 格式反序列化都快速高效。

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

流式数据格式是可以按顺序逐块处理,而无需等待整个数据集的格式。当数据以流式格式传输时,接收系统可以在第一块数据到达时就开始处理。这可以加速数据传输,具体表现在以下几个方面:传输时间可以与处理时间重叠;接收系统可以更有效地利用内存;并且可以并行传输多个流,从而加快传输、反序列化和处理速度。

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

Arrow是一种流式数据格式。数据集可以在Arrow中表示为一系列具有相同模式的记录批次。Arrow定义了一个流式传输格式,包括模式以及一个或多个记录批次。接收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. 从1990年代到今天,网络性能的提升速度超过了CPU性能的提升速度。例如,在1990年代后期,主流桌面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的传输性能时,此处描述的其他属性通常比此属性更重要。