Apache Arrow 最容易被介绍成一个很快的列式库。这个说法没有错,只是比它真正改变生态的部分窄了一圈。Arrow 真正卖出去的东西,是一份内存契约。它给分析系统提供了一套共享语法,用来描述数组、空值、偏移、批次与流式传输,于是某个运行时里生成的表,可以直接交给另一个运行时继续处理。[1][2][3][4]

把 Arrow 放到 2026 年来读,这条线索最值得抓住。若只把它看成“又一个数据框架”,这个项目会显得有点发散:格式规范、IPC 消息、Flight RPC、C 接口、多语言绑定、计算内核,再加上一大片相邻生态。[1][2][3][4] 可一旦把 Arrow 看成一块协议面板,这些部件就都归了位。它真正重要,是因为多套引擎可以在同一种布局与同一种交接方式上相遇,把各自的差异留在这层之上,这层之下的转换成本随之收紧。

配图说明:题图使用 Wikimedia Commons 上 Wes McKinney 的真实会议照片。它适合本文,因为文章讨论的是一种到今天仍然带着创始人意图的架构选择:把分析型数据的内存布局标准化,让不同系统在互操作时沿着同一条边界继续合作。[7]

1. Arrow 的起点落在 table 之下的 buffer 层

Arrow 列式格式最硬的一点,在于它把抽象起点压得很低,低过 SQL table 与 dataframe API,直接落在物理数组布局上:连续 buffer、明确的空值表示,以及对变长值的固定规则。[2] 规范写得很直白,数组通常带着自己的 validity bitmap,而变长值则通过 offsets buffer 加 data buffer 表示。[2] 这正是 Arrow 能在分析引擎之间旅行的第一层原因。它先规定字节怎样摆,再让上层系统决定这些数据该叫什么。

这种布局纪律,是 Arrow 能被多方接受的第一步。一个定宽 primitive column,对应 values buffer 加上 validity state。[2] 一个字符串列,对应 offsets 再加 data,同样叠着空值位图。[2] 嵌套数组沿着同一套逻辑继续展开。只要系统先同意这层物理语法,分析状态在它们之间移动的代价就会明显下降。

规范里关于对齐的要求,把这种意图写得更清楚。Arrow 推荐对内存做对齐分配,并明确建议在合适时采用 64-byte 的对齐与 padding,这样实现就更容易贴合向量化执行与 CPU cache 行为。[2] 这也是 Arrow 性能声誉背后更扎实的一层。它的速度来自上层引擎足够相信 buffer 布局,于是可以直接用紧凑循环读很多值,省掉额外的重组动作。

record batch 则是下一块关键砖头。Arrow 给出的单位,是 schema 加上一批有边界的列式数组,这些 batch 可以被流式传输、被交接、被重新组合。[2] 正因为单位是 batch,Arrow 才会同时在单进程内部与跨传输边界的场景里都显得自然。系统每次都能递过去一块可以工作的分析状态,省掉临时拼装行式协议的工夫。

2. C Data Interface 把格式规范变成了真正可落地的交接契约

只靠格式规范,本来还不够。很多项目都写过自己的存储格式或序列化规范,最后并没有变成日常互操作。Arrow 的 C Data Interface,是这件事真正落到工程现场的地方。

它的 rationale 写得很坦白:不少项目想做 Arrow 兼容的数据交换,却又希望继续保持自己的实现独立性。[3] 这个接口就是为此存在的。它定义了 plain C 的 schema 与 array 结构,带着由 producer 提供的 release callback,以及 opaque private data,让生命周期规则能够跨库边界传递,让不同运行时继续保持独立。[3] 也就是说,这份契约讲的已经越过“这些 buffer 在语义上相同”,进入“这些 buffer 跨边界移动时,所有权怎么交、释放何时发生、双方怎样不踩坏彼此的内存”。

这一层比“zero-copy”三个字更重要。zero-copy 的价值,落在接收端能够正确理解布局与生命周期的前提上。若这层前提松动,复制会在后面某一层重新出现,内存安全也会变成隐藏成本。Arrow 的 C Data Interface 存在,就是因为互操作除了 buffer 形状相同,还要让 producer 与 consumer 对“谁拥有这块数据、何时释放”达成纪律。[3]

规范也反复说明,这个接口同时服务一次性交付与持续对话。它写到 schema 可以在对话开始时先传一次,后面的 batch 再以 array 形式逐步交付,并且把自己的设计目标与 Python buffer protocol 的成功经验并列起来,强调的是低适配成本的数据交换。[3] 到了这里,Arrow 已经超出单纯的内存格式,更像分析型系统之间的通用交接语言。

3. Flight 在网络边界上继续沿用同一份契约

Arrow Flight 之所以有用,恰恰因为它没有在数据离开进程之后把这份契约扔掉。

Flight 规范把它定义成一个基于 gRPC 与 Arrow IPC 的高性能 RPC 框架,核心对象是 Arrow record batches 的流,加上用于发现与检查流的元数据方法。[4] 这会改变人看待它的方式。Flight 更像一条范围收得很紧、落点很扎实的通道:只要两边本来就理解 Arrow 形状的数据,就可以继续用 Arrow 形状的单位对话,同时再叠加传输、端点发现与应用特定控制方法。[4]

这条边界需要说得很明白。Flight 带来的改进,落在数据越过网络边界时仍然保持 Arrow 形状。字节依然要移动,帧结构依然存在,传输问题也继续保留在现场;变化发生在更具体的位置:生产端可以沿着列式 batch 往前推送,消费端也继续按 record batches 承接它们。[2][4]

所以 Flight 特别适合某些数据服务形态。协议可以用 descriptor 描述数据集,用 FlightInfo 暴露端点,再把 Ticket 交给客户端,通过 DoGet 拉取一条流,或者通过 DoPut 上传一串批次。[4] 它把意见鲜明地放在正确位置上:让传输语义尽量贴着 Arrow 的数据模型,上面那一层少生出另一套完全不同的脑内地图。

4. 真正的生态证据,落在不同引擎持续选择在 Arrow 这一层相遇

对 Arrow 最有力的证明,落在别的工具愿意把 Arrow 兼容性当成工程桥梁。这层意愿本身就比一张性能图更有分量。

DuckDB 的 Python 指南写得很明确,DuckDB 可以直接对 Arrow Tables、Arrow Datasets 以及 RecordBatchReaders 跑 SQL,来源既可以是 PyArrow,也可以来自 pandas 与 Polars,数据可以沿着原本的 Arrow 边界进入查询路径。[5] 这其实是一个很重的架构信号。DuckDB 还是 DuckDB,它保留自己的执行引擎、优化器与存储选择,但在交换层上,它愿意在数据原本所在的 Arrow 边界与别的系统会合。[5]

Polars 从 dataframe 这一侧给出的判断也几乎一致。它的 Arrow producer/consumer 指南推荐使用 Arrow PyCapsule Interface 与底层的 Arrow C Data Interface,因为这套接口可以带来 zero-copy 交换,同时把 pyarrow 依赖与对 Polars 的直接依赖都压到更轻的位置。[6] 这正是成功契约面该有的样子。系统各自保留路线图,也各自保留实现选择,只要在数据跨边界的时候同意那条边界长什么样。

顺着这些文档往下读,我更愿意把 Arrow 的战略价值理解成一种生态去耦合能力。[3][5][6] 它让不同引擎在契约之上继续保持专门化,在契约之下却可以用更低成本组合。DuckDB 继续做查询引擎,Polars 继续做 dataframe 引擎,某个服务层或传输层也继续保持独立,分析型 batch 在它们之间移动时,各家私有内存方言的坚持会明显减少。

5. Arrow 最适合在哪里发力,团队又最容易在什么地方误读它

Arrow 最强的场景,是团队确实存在一条多引擎分析路径:Python 加 Rust,dataframe 加查询引擎,本地进程加远程数据服务,或者一个库生产 batch,另一个库消费 batch。[1][3][4][5][6] 在这些环境里,共享布局能省掉大量原本只是搬运与重塑形状的浪费。

Arrow 的适用面,集中在分析型边界。存储设计、OLTP 的行式问题、事务协调,以及应用级 schema 演进,仍然各自留在系统另一层。zero-copy 的成立范围也需要按场景逐条核对:类型一致性、所有权处理、嵌套值边缘情况,以及接口实现完整度,都会决定复制或不稳定是否重新回来。[2][3][6]

所以真正该问的问题是更窄的一句:系统里哪一条边界正在为数据翻译反复付税,而边界两边是否真的愿意同时接受 Arrow 的布局与生命周期规则? 若答案是愿意,Arrow 往往能拿掉一大截摩擦;若这层共识还没形成,口号就会先跑到架构前面。

Apache Arrow 真正的成功,不在于它又造出了一套更快的库,而在于它造出了一份足够结实的契约,让别的系统愿意持续到这里来会合。

来源

  1. Apache Arrow,《Overview》——项目范围、语言覆盖,以及 Arrow 作为通用列式内存层的定位。
  2. Apache Arrow Format,《The Arrow Columnar Format Specification》——validity bitmap、offsets buffer、record batches 与 64-byte 对齐规则。
  3. Apache Arrow Format,《The Arrow C Data Interface》——producer/consumer 交换、release callback、schema 与 array 交接,以及低适配成本目标。
  4. Apache Arrow Format,《Arrow Flight RPC》——基于 gRPC 的传输、record-batch 流、descriptor、ticket 与 DoGet/DoPut 模式。
  5. DuckDB 文档,《SQL on Arrow》——DuckDB 直接查询 Arrow Tables、Datasets 与 RecordBatchReaders 的方式。
  6. Polars 用户指南,《Arrow producer/consumer》——Arrow PyCapsule 与 C Data Interface 互操作、zero-copy 交换,以及更轻的依赖面。
  7. Wikimedia Commons 文件页——本文使用的 Wes McKinney 会议照片来源。