Apache Arrow

Apache Arrow 最初是一种列式数据的内存格式规范,在 Java 和 C++ 语言中都有实现。这种内存格式对于现代硬件上的向量化处理是有效的,比如支持 SIMD(单指令多数据)的 CPU 和 GPU。

拥有标准化的内存数据格式有几个好处:

  • 高级语言(如 Python 或 Java)可以通过传递指向数据的指针的方式来调用使用低级语言(如 Rust 或 C++)编写的计算密集型任务,而不是以不同的格式复制数据,这将是非常昂贵的。
  • 数据可以在进程之间有效地传输,没有太多的序列化开销,因为内存数据格式也是网络传输数据格式(尽管数据也可以被压缩)。
  • 它会让在数据科学和数据分析领域的各种开源和商业项目之间建立连接器、驱动程序和集成变得更加容易,并允许开发人员使用他们最喜欢的语言来利用这些平台。

Apache Arrow 现在有许多编程语言的实现,包括 C, C++, C#, Go, Java, JavaScript, Julia, MATLAB, Python, R, Ruby, 和 Rust。

Arrow 内存模型

Arrow网站 上面有内存模型的详细描述,但从根本上来说,每一列都由一个持有原始数据的向量表示,同时还有单独的向量表示空值和可变宽度类型的原始数据中的偏移。

进程间通信 (IPC)

如前所述,数据可以通过传递一个指向数据的指针在进程之间传递。然而,接收进程需要知道如何解释这些数据,因此需要定义一种 IPC 格式,用于交换元数据,如表结构信息。Arrow 使用 Google Flatbuffers 来定义元数据格式。

计算内核

Apache Arrow 的使用范围已经扩大到提供针对数据进行表达式求值的计算库。Java、C++、C、Python、Ruby、Go、Rust 和JavaScript 的实现都包含了用于在 Arrow 内存上进行计算的计算库。

由于本书主要是使用 Java 的实现,值得指出的是,Dremio 最近捐赠了 Gandiva 项目,这是一个 Java 库,可以将表达式编译成 LLVM 并支持 SIMD。JVM 开发者可以将操作委托给 Gandiva 库,并获得性能提升,这在原生 Java 中是不可能的。

Arrow Flight 协议

最近,Arrow 定义了一个 Flight 协议,用于在网络上有效地传输 Arrow 数据。Flight 是基于 gRPC 和 Google Protocol Buffers 的。

Flight 协议定义了一个具有以下方法的 FlightService:

Handshake

客户端和服务器之间进行握手。根据服务器的情况,握手可能需要确定未来操作中需要使用的令牌。请求和响应都是流,以允许多次往返,这取决于认证机制。

ListFlights

根据特定的条件获取可用的数据流的列表。大多数 flight 服务将公开一个或多个随时可供检索的数据流。这个 API 允许列出可供消费的数据流。用户也可以提供一个查询条件,这个条件可以限制通过该接口列出的数据流的子集。每个 flight 服务都允许自定义处理这些条件的方式。

GetFlightInfo

对于给定的 FlightDescriptor,获得关于如何消费该 flight 的信息。如果该接口的消费者已经可以确定要消费的具体 flight,那么这将是一个有用的接口。这个接口还可以让消费者通过指定的描述符生成一个 flight 流。例如,flight 描述符可能是一个包括 SQL 语句或将被执行的 Pickled Python 操作的东西。这些情况下,描述符将不会在 ListFlights 方法提供的可用数据流列表中出现,但会允许在特定的 flight 服务定义的时间内消费。

GetSchema

对于一个给定的 FlightDescriptor,获得 Schema.fbs::Schema 中描述的表结构。当消费者需要 flight 数据流的表结构时就会用到这个接口。与 GetFlightInfo 类似,这个接口可能会生成一个之前在 ListFlights 中没有的新 flight。

DoGet

检索与 referenced ticket 关联的特定描述符相关的单一数据流。一个 flight 可以由一个或多个数据流组成,其中每个流可以使用单独的 opaque ticket 来检索,flight 服务会用它来管理一个数据流的集合。

DoPut

将一个数据流推送到与某个 flight 服务的 flight 数据流上面。这个接口允许 flight 服务的客户端上传一段流式数据。不同的 flight 服务将会允许客户端消费者对每个描述符上传一段流数据或者无限数量。在后一种情况下,flight 服务需要实现一个 "密封 "动作,一旦所有数据流被上传,就可以 apply 这个描述符。

DoExchange

为给定的描述符打开一个双向数据通道。这允许客户端在一个单一的逻辑数据流中发送和接收任意的 Arrow 数据和应用相关的元数据。与 DoGet / DoPut 相比,这更适合于客户端将计算(而不是存储)卸载给 Flight 服务。

DoAction

除了可能的 ListFlights、GetFlightInfo、DoGet、DoPut 操作外, flight 服务还可以支持任意数量的简单操作。DoAction 允许 flight 客户端对 flight 服务执行一个特定的操作。一个操作包括不透明的请求和响应对象,这些对象是针对正在进行的行动类型的。

ListActions

一个 flight 服务公开了它拥有的所有可用的操作类型及其描述。这使不同的 flight 消费者能够理解 flight 服务所能提供的能力。

Arrow Flight SQL

有一项提议是在 Arrow Flight 中增加 SQL 功能。在撰写本文时(2021年1月),有一个用 C++ 实现的 PR,其跟踪的问题是 ARROW-14698

查询引擎

DataFusion

Arrow 的 Rust 实现包含一个名为 DataFusion 的内存查询引擎,该引擎在 2019 年被捐赠给该项目。这个项目正在迅速成熟,并且越来越受欢迎。例如,InfluxData 正在使用 DataFusion 构建下一代 InfluxDB 的内核。

Ballista

Ballista 是一个主要用 Rust 实现的分布式计算平台,由 Apache Arrow 驱动。它建立了一个架构,允许其他编程语言(如Python、C++和Java)作为一等公民被支持,而不需要付出序列化带来的成本损耗。

Ballista 的基础技术包括:

  • Apache Arrow,用于内存模型和类型系统。
  • Apache Arrow Flight 协议,用于进程之间的高效数据传输。
  • Google Protocol Buffers,用于序列化查询计划。
  • Docker,用于将执行器与用户自定义代码打包在一起。
  • Kubernetes,用于部署和管理执行器docker容器。

Ballista 在 2021 年被捐赠给 Arrow 项目。尽管它能够以良好的性能运行流行的 TPC-H 基准的一些查询,但还没有准备好用于生产。

C++ 查询引擎

对于 C++ 实现,目前正在实现一个查询引擎,目前的重点是实现高效计算原语和 Dataset API。

本书的电子版、MOBI和PDF格式也可从 https://leanpub.com/how-query-engines-work 购买。

Copyright © 2020-2022 Grove Enterprises, LLC。保留所有权利。