Apache Arrow、Parquet、Flight 及其生态系统是 OLAP 的游戏规则改变者
作者:Paul Dix / 用例, 开发者
2020 年 4 月 16 日
导航至
Apache Arrow,一种内存列式数据格式规范,以及相关项目:Parquet 用于压缩磁盘数据,Flight 用于高效 RPC,以及其他用于内存查询处理的项目,可能会塑造未来 OLAP 和数据仓库 系统的发展方向。这主要将由项目之间的互操作性承诺以及大数据系统内外数据推送和拉取的巨大性能提升所驱动。随着像 S3 这样的对象存储作为通用数据湖,OLAP 项目需要一个通用的数据 API,Parquet 正是代表。对于数据科学和查询工作负载,他们需要一个优化的通用 RPC,用于拉取数百万条记录,以执行更复杂的分析和机器学习任务。
在这篇文章中,我将介绍这些领域中的每一个,以及为什么我认为 Apache Arrow 项目伞代表了当前的和未来的大数据、OLAP 和数据仓库项目将围绕其协作和创新的通用 API。最后,我将总结一些关于这些项目的现状以及未来发展方向的思考。
Apache Arrow
Apache Arrow 是一种内存列式数据格式。它旨在利用现代 CPU 架构(如 SIMD)在列式数据上实现快速性能。它是向量化分析查询的理想选择。Arrow 规范为可以在进程和查询处理库之间共享的列式和嵌套数据提供了标准的内存布局。它是处理内存(或 MMAP)数据的基本构建块,将所有内容联系在一起。它专为零拷贝语义而设计,使数据移动尽可能快速高效。
持久性、批量数据和 Parquet
数据即 API。我相信在 2010 年谈论面向服务的设计以及构建通过消息总线(如 RabbitMQ,后来是 Kafka)交互的松耦合系统时,其他人也在我之前说过这句话,但我自己也说过。我当时的意思是,您通过消息队列传递的数据充当了通过这些队列相互集成的服务的 API。与 API 一样,这些数据需要具有通用的序列化格式,并且其架构应该进行版本控制。更广泛地说,我试图强调数据交换的重要性,这对于数据处理和分析系统来说,与对于服务一样重要。
从历史上看,OLAP 和数据仓库系统共享的主要接口点是某种形式的 SQL(通常是有限的)作为查询接口,以及 ODBC 作为 RPC API。SQL 在查询方面工作得足够好,但 ODBC 对于这些系统内外的批量数据移动效率不高(更新:看起来这通过 turbodbc 的工作得到了改进,但仍然不如原生 Arrow 可以提供的那么好)。批量数据移动很快成为机器学习、数据科学或在 SQL 功能之外执行更复杂操作的必备条件。早期,批量数据移动不是互操作性的主要关注点,因为每个 OLAP 系统都必须关心自己的存储,并且通常作为孤岛运行。然而,随着像 Hadoop 这样的数据湖变得普遍,OLAP 系统需要一种通用格式来使用其他后端存储系统。
对通用文件持久性格式的需求因对象存储和像 S3 这样的 API 作为共享数据湖的兴起而加速。系统需要一种从对象存储批量推送和拉取数据的方法。虽然 CSV 和 JSON 文件对用户友好,但它们在大小和吞吐量方面都非常低效。Apache Parquet 从 Cloudera 出现,成为压缩列式和分层数据的通用文件格式。Parquet 现在无处不在,已被广泛接受为 Hive、Drill、Impala、Presto、Spark、Kudu、Redshift、BigQuery、Snowflake、Clickhouse 等大数据系统中数据的输入(通常也是输出)格式。
过去几年的发展为 Parquet 带来了更多的实现和可用性,对其他语言中读取和写入文件的支持越来越多。目前,看起来 C++、Python(与 C++ 实现的绑定)和 Java 在 Arrow 项目中对读取和写入 Parquet 文件具有一流的支持。R 看起来对读取有很好的支持,但我不确定写入方面的情况(更新:R 的写入支持也很棒,因为它使用了相同的 C++ 库)。Ruby 看起来也有绑定。Rust 有一个用于读取 Parquet 文件的部分实现。在顶级语言中添加一流的支持将是巩固 Parquet 作为压缩列式数据持久性格式的普遍地位的必要步骤。然而,Parquet 的性能提升以及与其他系统互操作性的承诺将激励开发人员完成这项工作。Wes McKinney 在 2019 年 10 月发布的比较 Parquet 性能的文章很好地回顾了性能提升的情况。
RPC 和将数据移动到您需要的地方
现在我们有了一种通用的格式来移动持久化的压缩数据,我们需要一种更高效的格式来传输这些数据,而无需序列化和反序列化的开销。这就是 Apache Arrow Flight,一个快速数据传输框架的目标。它定义了一个 gRPC API,该 API 传输包装在 FlatBuffers 中的 Arrow Array 数据,以描述元数据,如模式、字典和记录批次之间的中断(与同一模式和行计数匹配的 Arrow Array 数据集合)。我无法比我上面链接的 Wes McKinney 的文章更好地描述它,所以如果您需要了解更多信息,请阅读该文章。
关于 Flight 有一些令人兴奋的事情。首先,它很快。为了试用,我用 Rust 编写了一个简单的测试程序,将 flight 数据发送给 Python 消费者。我生成的测试数据的模式如下所示
let fields: Vec<Field> = vec![
Field::new("host", DataType::Utf8, false),
Field::new("region", DataType::Utf8, false),
Field::new("usage_user", DataType::Float64, true),
Field::new("usage_system", DataType::Float64, true),
Field::new("timestamp", DataType::Int64, false),
];
我们有一个包含五个字段的数据表:两个字符串、两个 float64 和一个 int64。我让程序生成 100 个记录批次,每个批次包含 100,000 行,总共 1000 万条记录。每个记录批次代表来自单个主机的测试数据,来自两个区域之一。我将其连接到一个简单的 Flight API,该 API 将获取这些数据。这是一个用 Rust 编写的 Flight 服务器的示例框架。然后我在 Jupyter Notebook 中编写了一个 Python 脚本,连接到本地主机上的这个服务器并调用 API。代码非常简单
cn = flight.connect(("localhost", 50051))
data = cn.do_get(flight.Ticket(""))
df = data.read_pandas()
在三行 Python 代码中(不包括导入),我们连接到 Flight 服务器,执行了 get 操作,并将结果读取到 Pandas DataFrame 中。我传递给 flight.Ticket 的空字符串是您可以放置有效负载(如 JSON blob)的地方,供您的 API 服务器解释请求并执行查询或类似操作。对于此测试,我只想返回我内存中的数据。
在我的本地主机上,get 请求大约花费了 311 毫秒 +/- 50 毫秒,进行了几次不同的运行。通过网络传输的数据大约为 452MB。将这些数据读取到 Pandas DataFrame 中大约花费了 1 秒 +/- 100 毫秒。因此,现在我们在不到 1.5 秒的时间内就准备好在我们最喜欢的数据科学工具包中使用 1000 万条记录。考虑到我的示例数据有很多重复的字符串,我们或许可以通过在 Arrow 中使用 Dictionary 类型和在 Flight 中使用 DictionaryBatch 来做得更好。Rust 对此的支持目前尚 부족,但我正在努力在未来一周左右提交我自己对它的支持。
关于 Flight 的第二个优点是与其他系统集成的潜力。它可以充当跨多种语言的标准 RPC,在数据科学和分布式查询执行等任务中提供帮助。虽然 Arrow 是一个具有开放治理的 Apache 项目,但在我这个局外人看来,它主要由 Wes McKinney 及其在 Ursa Labs 团队的努力所驱动,Ursa Labs 是他为推动 Arrow 发展而建立的非营利组织。是的,还有其他人参与和贡献,如来自 Dremio 和 Cloudera 的人员,以及其他,但 Ursa 的努力看起来是驱动力。同样值得注意的是,R 数据科学界的 Hadley Wickham 也是顾问。
我之所以提到 Wes 和 Hadley 的参与,是因为这意味着您有两位开源数据科学领域最大的贡献者为 Arrow 及其项目做出贡献。这意味着 Python(Wes 是 Pandas 的创建者)和 R(Hadley 是 Tidyverse 的创建者)对 Arrow 和 Flight 都有一流的支持,并将随着它们的不断开发而发展。这使得支持 Arrow 和 Flight 的服务成为各地数据科学家的显而易见的选择。
快速内存查询
更加新兴且尚未完全开发的是在库中添加查询功能的努力,该库使用 Arrow 作为其格式。在 2019 年 3 月,Wes 和其他 Arrow 贡献者编写了一些 使用 Arrow 的列式查询执行引擎的设计要求。这是在 Dremio 贡献了一个名为 Gandiva 的项目以在 Arrow 保护伞下生存之后。Gandiva 是一个基于 LLVM 的 Arrow 分析表达式编译器。
值得注意的是,这两个项目都是用 C++ 编写的。这意味着与 Arrow 一样,它们可以嵌入到您选择的任何高级语言中。还有一个 名为 DataFusion 的 Rust 原生 Arrow 查询引擎,由 Andy Grove 于去年贡献。因为它是用 Rust 编写的,所以它也可以嵌入到其他高级语言中。
构建 Arrow 原生查询引擎的努力还处于早期阶段,但我认为它们完成了 Arrow 项目集提供的一系列功能的闭环。您不仅可以在服务之间来回发送大型数据集并持久化它们,还可以使用它们,并在您选择的语言中以出色的性能进行查询。
这些查询引擎也代表了一个领域,更多的 OLAP 和 数据仓库 供应商可能会最终贡献和协作以分担工作量。大规模运行数据处理系统非常困难,并且需要在查询引擎之外编写大量代码。这意味着供应商将能够为自由许可的查询引擎做出贡献,而不会泄露其核心知识产权。
现在的状况以及意味着什么
Parquet 格式似乎是这套工具中最成熟的东西。然而,在许多语言中,对读取和写入 Parquet 文件的支持仍处于早期阶段。
Arrow 内存列式格式虽然也存在了一段时间,但仍在快速成熟。他们最近推出的 Arrow C Data Interface 应该使其他语言的库作者更容易与 Arrow 进程内库和项目进行交互。
Flight 协议仍在发展中,并且在各种语言中的支持都非常早期(Flight 是去年 10 月推出的)。查询引擎甚至比这更早。
尽管如此,我对这些项目的未来发展方向感到非常兴奋。它们共同代表了一组工具,可用于构建用于数据科学、分析和大规模数据处理的可互操作组件。它们也是可以用来构建新型数据仓库和分布式 OLAP 引擎的原语。
在过去的 15 年中,开源大数据生态系统一直由用 Java 编写的工具主导。拥有用 C/C++ 和 Rust 编写的库和原语基础层,为全新的工具生态系统打开了大门。我敢打赌,Rust 将成为构建服务的首选语言,这些服务可以通过网络和分布式服务访问 Arrow 及其组件。OLAP 将不仅仅是关于大规模分析查询,还将是关于在毫秒内回答的近实时查询。
开发者工具和技术是一场持续颠覆的游戏。使用像 S3 这样的对象存储作为数据湖,在短时间内完全颠覆了 Hadoop 生态系统。容器化和 Kubernetes 在一夜之间完全颠覆了编排和部署生态系统。随着 Arrow 及其相关项目的成熟,数据仓库/大数据生态系统中可能会出现另一次颠覆。这将由对提高互操作性的渴望以及一组可以嵌入到任何地方的新工具所驱动。将其与在 Kubernetes 等环境中运行这些系统的新设计要求相结合,我们可能会在未来五年内看到该领域出现一组非常不同的领导者。