Apache Arrow格式如何加速查询结果传输
已发布 2025年1月10日
作者 Ian Cook, David Li, Matt Topol, Sutou Kouhei [译]
翻译 原文(English)
这篇文章是解开Arrow作为数据库和查询引擎之间数据交换格式之谜的系列文章的第一篇。
“为什么这么慢?”
这是处理数据的人在等待查询结果时经常会想到的问题。有很多可能的答案。也许数据源没有被正确地分区。也许SaaS数据仓库的资源不足。也许查询优化器没有将SQL语句转换为高效的执行计划。
然而,在很多情况下,答案是使用了低效的协议来将查询结果传输到客户端。在2017年的一篇论文中,Mark Raasveldt和Hannes Mühleisen观察到,特别是当查询结果很大时,查询结果的传输通常会占据查询执行时间的大部分。然而,瓶颈并不在你预期的地方。
从源到目的地的查询结果传输分为以下三个步骤:
- 源:将结果从原始格式序列化为传输格式
- 源:以传输格式通过网络发送数据1
- 目的地:将传输格式的数据反序列化为易于处理的格式
在网络速度慢的时代,通常数据传输步骤(步骤2)是瓶颈。因此,几乎没有动机来加速序列化步骤(步骤1)和反序列化步骤(步骤3)。相反,重点是减少传输的数据量。在很多情况下,数据会被压缩以缩短传输时间。广泛使用的数据库连接API(ODBC和JDBC)和数据库客户端协议(MySQL的客户端-服务器协议和PostgreSQL的前端-后端协议)都是在这个时代设计的。然而,随着网络速度的提高和传输时间的缩短,瓶颈已经转移到序列化和反序列化步骤2。这对于生成大量结果的查询尤其如此。许多数据工程管道和数据分析管道经常执行这样的查询。
现在,许多查询结果仍然通过旧的API和协议传输。这些API和协议必须使用低效的传输格式,因此存在许多序列化和反序列化的开销。在2021年的一篇论文中,Tianyu Li等人展示了一个使用ODBC和PostgreSQL协议的例子。在这个例子中,总查询执行时间的99.996%都花在了序列化和反序列化上。这是一个极端的例子,但我们已经看到了很多花费超过90%的实例。现在,对于数据工程查询和数据分析查询,选择一种加速序列化和反序列化的传输格式有很强的动机。
这就是Arrow的作用!
Apache Arrow开源项目定义了一种数据格式。这种格式旨在加速查询结果传输时的序列化和反序列化。在许多情况下,不需要序列化和反序列化。自从2016年创建以来,Arrow格式以及围绕该格式构建的多语言工具箱已被广泛使用。然而,对于Arrow如何减少序列化和反序列化开销的技术细节,人们的理解并不多。为了解决这个问题,我们将解释Arrow格式的五个特点,这些特点使其能够减少序列化和反序列化的开销。
1. Arrow格式是列式存储
列式(Columnar)数据格式将每个列的值保存在内存中的连续区域中。这与行式数据格式相反。行式数据格式将每一行的值保存在内存中的连续区域中。

高性能的分析数据库、数据仓库、查询引擎和存储系统通常采用列式架构。这是为了快速执行常用的分析查询。最新的列式查询系统包括Amazon Redshift、Apache DataFusion、ClickHouse、Databricks Photon Engine、DuckDB、Google BigQuery、Microsoft Azure Synapse Analytics、OpenText Analytics Database (Vertica)、Snowflake、Voltron Data Theseus等。
同样,分析查询结果的许多目的地也采用列式架构。例如,目的地包括BI工具、数据应用程序平台、数据帧库和机器学习平台。列式BI工具包括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。FlatBuffers是Google创建的格式。FlatBuffers在任何介质上都具有相同的二进制结构。
这些设计决策使得Arrow不仅可以用作传输格式,还可以用作内存格式和磁盘格式。这与JSON和CSV等基于文本的格式以及Protocol Buffers和Thrift等序列化的二进制格式形成对比。这些格式使用专用的语法来编码数据。要将这些格式的数据加载到可在内存中使用的结构中,需要解析和解码数据。这与Parquet和ORC等二进制格式也形成对比。这些格式会进行编码和压缩以减少磁盘上的数据大小。要将这些格式的数据加载到可在内存中使用的结构中,需要展开和解码数据3。
这意味着,如果数据源系统在内存或磁盘上有Arrow格式的数据,则无需序列化就可以以Arrow格式通过网络传输数据。此外,数据接收系统可以从网络读取数据到内存或作为Arrow文件写入磁盘,而无需反序列化。
Arrow格式被设计为可以非常高效地进行分析操作的内存格式。因此,许多列式数据系统都采用Arrow作为内部内存格式。例如,Apache DataFusion、cuDF、Dremio、InfluxDB、Polars、Velox、Voltron Data Theseus等都采用了它。如果这些系统是数据源或数据接收器,则序列化和反序列化的开销将完全消除。对于许多其他列式数据系统,它们使用的专有内存格式与Arrow非常相似。在这些系统中,与Arrow格式的序列化和反序列化处理非常快速和高效。
4. Arrow格式是可流式传输的
可流式传输的数据格式可以按顺序一次处理一个块,而无需等待整个数据集。当数据以可流式传输的格式传输时,目标系统可以在第一个块到达后立即开始处理。这可以通过几种方式加速数据传输。例如,可以在处理数据的同时接收下一个数据。例如,目标系统可以更有效地使用内存。例如,可以并行传输多个流。这可以加速数据传输、数据反序列化和数据处理。
例如,CSV是一种可流式传输的数据格式。因为(如果包含)文件开头的标头包含列名,并且文件中随后的行可以按顺序处理。另一方面,Parquet和ORC不是可流式传输的数据格式。因为处理数据所需的模式和其他元数据位于文件末尾的页脚中。需要在开始处理之前下载整个文件(或者移动到文件末尾并单独下载页脚)4。
Arrow是一种可流式传输的数据格式。数据集可以用Arrow表示为具有相同模式的记录批次的列。Arrow定义了一种流式格式。在这种格式中,首先是模式,然后是一个或多个记录批次。接收Arrow流的系统可以按接收的顺序处理记录批次。

5. Arrow格式是通用的
Arrow已经成为用于在内存中处理表格数据的实际标准格式。Arrow是一种与语言无关的开放标准。提供了各种语言的Arrow数据处理库。例如,有C、C++、C#、Go、Java、JavaScript、Julia、MATLAB、Python、R、Ruby、Rust、Swift的官方库。用主流语言开发的应用程序可以支持以Arrow格式发送和接收数据的功能。JDBC等一些数据库连接API需要使用特定的语言运行时,但Arrow格式不需要这样做。
Arrow的通用性可以解决加速实际数据系统时的一个基本问题。该问题是,性能提升受到系统瓶颈的限制。这个问题被称为阿姆达尔定律。在实际的数据管道中,查询结果通常会流经多个阶段,每个阶段都有序列化和反序列化的开销。例如,如果你的数据管道有5个阶段,即使你消除了其中4个阶段的序列化和反序列化开销,你的系统也不会变得更快。因为剩余的一个阶段的序列化和反序列化将成为整个管道的瓶颈。
Arrow可以在任何技术栈上高效运行,因此有助于解决此问题。例如,如果有一个这样的数据流:具有NVIDIA GPU的worker的基于Scala的分布式后端→基于Jetty的HTTP服务器→使用基于Pyodide的浏览器前端的基于Node.js的机器学习框架来与用户交互的基于Rails的特征工程应用。没问题。Arrow库可以消除所有这些组件之间的序列化和反序列化开销。
总结
随着越来越多的商业和开源工具支持Arrow,没有或很少序列化和反序列化的快速查询传输变得越来越普遍。目前,许多数据库、数据平台和查询引擎都可以以Arrow格式传输查询结果。例如,Databricks、Dremio、Google BigQuery、InfluxDB、Snowflake、Voltron Data Theseus等商业产品,以及Apache DataFusion、Apache Doris、Apache Spark、ClickHouse、DuckDB等开源产品都支持它。这大大加快了速度。
- Apache Doris: 加速“20倍到数百倍”
- Google BigQuery: 加速“高达31倍”
- Dremio: 加速“10倍以上”
- DuckDB: 加速“38倍”
- Snowflake: 加速“高达10倍”
在数据接收端,处理数据的人可以通过使用基于Arrow的工具、库、接口和协议来最大限度地提高此加速。在2025年,更多的项目和供应商将支持ADBC标准,接收Arrow格式查询结果的工具数量将会增加。
请期待本系列的后续文章。我们将比较Arrow格式与其他数据格式,并解释客户端可以用来获取Arrow格式结果的协议和API。
-
传输格式也称为“线路格式”或“序列化格式”。 ↩
-
从1990年代到现在,网络性能的提升超过了CPU性能的提升。例如,在1990年代后期,主流的桌面CPU大约可以处理1GFLOPS,而典型的WAN连接速度为56Kb/s。现在,主流的桌面CPU大约可以处理100GFLOPS,而典型的WAN连接速度为1Gb/s。也就是说,CPU的性能提高了约100倍,而网络速度提高了约10000倍。 ↩
-
这并不意味着对于像存档存储这样的应用程序,Arrow比Parquet或ORC更快。在本系列的后续文章中,我们将更详细地比较Arrow格式与Parquet、ORC和其他格式。我们将解释它们是相互补充的。 ↩
-
这并不意味着CSV比Parquet或ORC可以更快地传输结果。在比较CSV和Parquet/ORC的传输性能时,通常这里描述的其他四个特征的影响大于此特征。 ↩