简介#

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中使用的字符串布局(有时也称为“German strings”)。

与经典二进制和字符串布局的主要区别在于视图缓冲区。 它包括字符串的长度,然后要么是内联出现的字符(对于小字符串),要么只是字符串的前 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 是一种嵌套数据类型(父级),它有一个子级(列表中值的 DataType)。

列表#

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

列表数据类型中的偏移量是 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.

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

结构体#

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

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

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

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

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

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

映射#

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

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

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

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

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

联合#

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

Arrow 定义了两种不同的联合数据类型,“密集”和“稀疏”。

密集联合#

密集联合对于混合类型数组中存在的每种数据类型都有一个子数组,并且它自己有两个缓冲区

  • 类型缓冲区: 保存数组的每个槽的数据类型 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.

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

稀疏联合#

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

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

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

字典编码布局#

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

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

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

游程编码布局#

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

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

  • 值数组: 没有重复的实际值(以及 null 值)。

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

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。

记录批次 一种连续的二维数据结构,由相同长度的数组的有序集合组成。

模式 字段的有序集合,它传达了对象(如记录批次或表)的所有数据类型。 模式可以包含可选的键/值元数据。

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

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

注意

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

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 数组或记录批次缓冲区堆叠在一起以进行序列化和反序列化。

  • 为了在同一进程中共享 Arrow 数据,使用了Arrow C 数据接口,旨在在同一进程中不同库之间零拷贝地共享相同的缓冲区。