简介#

Apache Arrow 的诞生源于对表格式数据表示和系统间交换标准的需求。采用这些标准可以降低数据序列化/反序列化的计算成本以及跨不同编程语言实现的系统的实现成本。

Apache Arrow 规范可以使用任何编程语言实现,但官方提供了许多语言的实现。实现包括使用该语言提供的构造和常见的内存数据处理算法(例如切片和连接)的格式定义。用户可以扩展和使用 Apache Arrow 实现提供的实用程序,选择他们喜欢的编程语言。一些实现走在前列,具有大量的内存分析数据处理算法。有关实现方式差异的更多详细信息,请参见状态页面。

除了最初的愿景之外,Arrow 还发展成为一个多语言库集合,用于解决与内存分析数据处理相关的问题。这涵盖了以下主题:

  • 零拷贝共享内存和基于 RPC 的数据移动

  • 读取和写入文件格式(如 CSV、Apache ORCApache Parquet

  • 内存分析和查询处理

Arrow 列式格式#

Apache Arrow 专注于表格式数据。例如,假设我们有可以组织成表格的数据

Diagram with tabular data of 4 rows and columns.

表格数据结构的示意图。#

表格数据可以使用基于行的格式或基于列的格式在内存中表示。基于行的格式按行存储数据,这意味着行在计算机内存中是相邻的

Tabular data being structured row by row in computer memory.

逐行保存在内存中的表格数据。#

在列式格式中,数据改为按列组织。这种组织方式使得分析操作(如过滤、分组、聚合等)由于内存局部性而更加高效。在处理数据时,CPU 访问的内存位置往往彼此靠近。通过使数据在内存中保持连续,它还可以实现计算的向量化。大多数现代 CPU 都有SIMD 指令(一个同时对多个值进行操作的指令),可以使用单个 CPU 指令并行处理和执行对向量数据的操作。

Apache Arrow 正在解决这个确切的问题。它是使用列式布局的规范。

Tabular data being structured column by column in computer memory.

相同的表格数据按列保存在内存中。#

在 Arrow 术语中,每列都称为一个数组。数组可以具有不同的数据类型,并且其值在内存中的存储方式因数据类型而异。这些值在内存中排列方式的规范称为物理内存布局。存储数组数据的连续内存区域称为缓冲区。一个数组由一个或多个缓冲区组成。

接下来的章节将介绍 Arrow 列式格式,解释不同的物理布局。该格式的完整规范可在Arrow 列式格式中找到。

对 Null 值的支持#

Arrow 支持所有数据类型的缺失值或“null”:数组中的任何值在语义上都可能为 null,无论是原始数据类型还是嵌套数据类型。

在 Arrow 中,使用一个专用缓冲区(称为有效性(或“null”)位图)以及数据来指示数组中的每个值是否为 null:值为 1 表示该值不为 null(“有效”),而值为 0 表示该值为 null。

此有效性位图是可选的:如果数组中没有缺失值,则不需要分配缓冲区(如下面图表中的第 1 列)。

注意

由于使用了最低有效位编号,我们在 8 位组中从右到左读取有效性位图。

这也是我们在此文档中包含的图表中表示有效性位图的方式。

原始布局#

固定大小原始布局#

原始列表示一个值数组,其中每个值都具有相同的物理大小(以字节为单位)。使用固定大小原始布局的数据类型有,例如,有符号和无符号整数数据类型、浮点数、布尔值、十进制和时间数据类型。

Diagram is showing the difference between the primitive data type presented in a Table and the data actually stored in computer memory.

原始数据类型的物理布局图。#

注意

布尔数据类型使用原始布局表示,其中值以位而不是字节进行编码。这意味着物理布局包括一个值位图缓冲区,并可能包括一个有效性位图缓冲区。

Diagram is showing the difference between the boolean data type presented in a Table and the data actually stored in computer memory.

布尔数据类型的物理布局图。#

注意

Arrow 还具有 Null 数据类型的概念,其中所有值都为 null。在这种情况下,不会分配任何缓冲区。

可变长度二进制和字符串#

与固定大小原始布局相反,可变长度布局允许表示一个数组,其中每个元素可以具有可变的大小(以字节为单位)。此布局用于二进制和字符串数据。

二进制或字符串列中所有元素的字节都连续存储在单个缓冲区或内存区域中。为了知道列的每个元素的起始位置和结束位置,物理布局还包括整数偏移量。偏移量缓冲区始终比数组长一个元素。最后两个偏移量定义了最后一个二进制/字符串元素的开始和结束位置。

二进制和字符串数据类型共享相同的物理布局。它们之间唯一的区别在于,假定字符串类型的数组包含有效的 UTF-8 字符串数据。

二进制/字符串和大型二进制/字符串之间的区别在于偏移量数据类型。在前一种情况下为 int32,在后一种情况下为 int64。

使用 32 位偏移量的数据类型的限制是每个数组的最大大小为 2GB。仍然可以使用非大型变体处理更大的数据,但随后需要多个块。

Diagram is showing the difference between the variable length string data type presented in a Table and the data actually stored in computer memory.

可变长度字符串数据类型的物理布局图。#

可变长度二进制和字符串视图#

此布局是可变长度二进制布局的替代方案,它改编自慕尼黑工业大学的 UmbraDB,并且类似于 DuckDBVelox 中使用的字符串布局(有时也称为“德国字符串”)。

与经典二进制和字符串布局的主要区别在于视图缓冲区。它包括字符串的长度,然后是其字符内联(对于小字符串)或仅是字符串的前 4 个字节以及到多个数据缓冲区之一的偏移量。因为它使用偏移量和长度来引用数据缓冲区,因此无需将所有元素的字节连续存储在单个缓冲区中。这使得可以将可变长度元素无序写入数组。

这些属性对于高效的字符串处理非常重要。前缀能够为字符串比较提供有利的快速路径,字符串比较通常在前四个字节中确定。选择元素是对固定宽度视图缓冲区的简单“收集”操作,无需重写值缓冲区。

Diagram is showing the difference between the variable length string view data type presented in a Table and the data actually stored in computer memory.

可变长度字符串视图数据类型的物理布局图。#

嵌套布局#

嵌套数据类型引入了父数组和子数组的概念。它们表达了嵌套数据类型结构中物理值数组之间的关系。

嵌套数据类型依赖于一个或多个其他子数据类型。例如,List 是一种嵌套数据类型(父类型),它有一个子类型(列表中值的类型)。

List#

列表数据类型允许表示一个数组,其中每个元素都是同一数据类型的元素序列。该布局类似于可变大小的二进制或字符串布局,因为它具有一个偏移量缓冲区,用于定义每个元素的起始和结束位置的值序列,所有值都连续存储在值子数组中。

列表数据类型中的偏移量为 int32,而大型列表中的偏移量为 int64。

Diagram is showing the difference between the variable size list data type presented in a Table and the data actually stored in computer memory.

可变大小列表数据类型的物理布局图。#

固定大小列表#

固定大小列表是可变大小列表的一种特殊情况,其中每个列槽都包含固定大小的序列,这意味着所有列表的大小都相同,因此不再需要偏移量缓冲区。

Diagram is showing the difference between the fixed size list data type presented in a Table and the data actually stored in computer memory.

固定大小列表数据类型的物理布局图。#

列表视图#

与列表类型相反,列表视图类型还具有一个大小缓冲区和一个偏移量缓冲区。偏移量继续指示每个元素的起始位置,但现在大小保存在单独的大小缓冲区中。由于大小不再从连续偏移量派生,因此允许无序偏移量。

Diagram is showing the difference between the variable size list view data type presented in a Table and the data actually stored in computer memory.

可变大小列表视图数据类型的物理布局图。#

结构体#

结构体是由字段的有序序列(数据类型和名称)参数化的嵌套数据类型。

  • 每个字段都有一个子数组

  • 子数组是独立的,不需要在内存中彼此相邻。它们只需要具有相同的长度。

可以将单个结构体字段视为键值对,其中键是字段名,子数组是其值。字段(键)保存在 schema 中,特定字段(键)的值保存在子数组中。

Diagram is showing the difference between the struct data type presented in a Table and the data actually stored in computer memory.

结构体数据类型的物理布局图。#

Map#

Map 数据类型表示嵌套数据,其中每个值都是可变数量的键值对。它的物理表示形式与 {key, value} 结构体的列表相同。

结构体和 Map 数据类型之间的区别在于,结构体将键保存在 schema 中,要求键为字符串,并且值存储在子数组中,每个字段对应一个子数组。可以有多个键,因此可以有多个子数组。另一方面,Map 有一个子数组,其中包含所有不同的键(因此所有键都需要具有相同的数据类型,但不一定是字符串),以及第二个子数组,其中包含所有值。这些值需要具有相同的数据类型;但是,该数据类型不必与键的数据类型匹配。

此外,Map 将结构体存储在列表中,并且由于列表是可变形状的,因此需要一个偏移量。

Diagram is showing the difference between the map data type presented in a Table and the data actually stored in computer memory.

Map 数据类型的物理布局图。#

Union#

Union 是一种嵌套数据类型,其中 Union 中的每个槽都具有一个值,其数据类型选自可能的 Arrow 数据类型的子集。这意味着 Union 数组表示混合类型的数组。与其他数据类型不同,Union 没有自己的有效性位图,空值由子数组确定。

Arrow 定义了两种不同的 Union 数据类型:“dense”(密集)和“sparse”(稀疏)。

密集 Union#

密集 Union 为混合类型数组中存在的每种数据类型都有一个子数组,以及它自己的两个缓冲区

  • 类型缓冲区: 保存数组每个槽的数据类型 ID。数据类型 ID 通常是子数组的索引;但是,数据类型 ID 和子索引之间的关系是数据类型的一个参数。

  • 偏移量缓冲区: 保存每个数组槽相对于各自子数组的偏移量。

Diagram is showing the difference between the dense union data type presented in a Table and the data actually stored in computer memory.

密集 Union 数据类型的物理布局图。#

稀疏 Union#

稀疏 Union 的结构与密集 Union 相同,只是省略了偏移量缓冲区。在这种情况下,每个子数组的长度都等于 Union 的长度。

Diagram is showing the difference between the sparse union data type presented in a Table and the data actually stored in computer memory.

稀疏 Union 数据类型的物理布局图。#

字典编码布局#

当数据中存在许多重复值时,字典编码可能很有效。这些值由引用字典的整数表示,该字典通常由唯一值组成。

Diagram is showing the difference between the dictionary data type presented in a Table and the data actually stored in computer memory.

字典数据类型的物理布局图。#

游程编码布局#

游程编码非常适合表示包含相同值序列的数据。这些序列称为游程。游程编码数组没有自己的缓冲区,但有两个子数组

  • 游程结束数组: 保存数组中每个游程结束的索引。游程结束的数量与其父数组的长度相同。

  • 值数组: 不重复的实际值(以及空值)。

请注意,父数组的空值严格在值数组中表示。

Diagram is showing the difference between the run-end encoded data type presented in a Table and the data actually stored in computer memory.

游程编码数据类型的物理布局图。#

另请参阅

所有 Arrow 数据类型 表格。

Arrow 术语概述#

物理布局 用于表示内存中数组值的规范。

缓冲区 内存中具有给定长度(以字节为单位)的连续区域。缓冲区用于存储数组的数据。有时我们使用缓冲区中元素的数量的概念,只有在我们知道包装此特定缓冲区的数组的数据类型时才能使用。

数组 具有已知长度的连续的一维值序列,其中所有值都具有相同的数据类型。一个数组由零个或多个缓冲区组成。

分块数组 具有已知长度的非连续一维值序列,其中所有值都具有相同的数据类型。由零个或多个数组组成,即“块”。

注意

分块数组是特定于某些实现(例如 Arrow C++ 和 PyArrow)的概念。

RecordBatch 连续的二维数据结构,由相同长度的数组的有序集合组成。

Schema 字段的有序集合,用于传达诸如 RecordBatch 或 Table 之类的对象的所有数据类型。Schema 可以包含可选的键/值元数据。

字段 字段包括字段名称、数据类型、可空标志和 RecordBatch 中特定列的可选键值元数据。

由分块数组的有序集合组成的非连续二维数据块。所有分块数组都具有相同的长度,但可能具有不同的类型。不同的列可能以不同的方式分块。

注意

表是特定于某些实现(例如 Arrow C++ 和 PyArrow)的概念。例如,在 Java 实现中,表不是分块数组的集合,而是 RecordBatch 的集合。

A graphical representation of an Arrow Table and a Record Batch, with structure as described in text above.

另请参阅

更多术语,请参见术语表

扩展类型#

如果系统或应用程序需要使用自定义语义扩展标准 Arrow 数据类型,可以通过定义扩展类型来实现。

扩展类型的示例包括UUID固定形状张量扩展类型。

可以通过使用自定义类型名称和可选的序列化表示形式(字段元数据结构中的 'ARROW:extension:name''ARROW:extension:metadata' 键)注释任何内置的 Arrow 数据类型(“存储类型”)来定义扩展类型。

另请参阅

扩展类型文档。

规范扩展类型#

共享众所周知的扩展类型的定义是有益的,这样可以提高集成 Arrow 列式数据的不同系统之间的互操作性。因此,规范扩展类型在 Arrow 本身中定义。

另请参阅

规范扩展类型文档。

社区扩展类型#

这些是在特定领域内建立为标准的 Arrow 扩展类型。

示例

  • GeoArrow:用于表示矢量几何图形的 Arrow 扩展类型集合

共享 Arrow 数据#

Arrow 内存布局旨在成为一种通用的标准,用于表示内存中的表格数据,而不与特定实现绑定。Arrow 标准定义了两种协议,用于在应用程序之间明确且无歧义地通信 Arrow 数据

  • 在进程之间或通过网络共享 Arrow 数据的协议称为序列化和进程间通信 (IPC)。共享数据的规范称为 IPC 消息格式,它定义了如何将 Arrow 数组或 RecordBatch 缓冲区堆叠在一起以进行序列化和反序列化。

  • 要在同一进程中共享 Arrow 数据,请使用Arrow C 数据接口,该接口旨在在同一进程中的不同库之间以零复制方式共享同一缓冲区。