使用 Apache Arrow 进行内存分析——由 Apache Arrow 驱动

419 阅读17分钟

随着越来越多的项目采用或支持 Apache Arrow 作为其内部和外部通信格式,Arrow 正逐渐成为行业标准。在本章中,我们将了解一些以不同方式使用 Arrow 的项目。Arrow 提供的灵活性使其能够在各种环境中满足不同的用例,许多开发者正在利用这一优势。当然,Arrow 广泛应用于许多分析引擎项目,但它也用于从机器学习到浏览器中的数据可视化等不同场景中。

随着不断涌现的新项目和应用,我们将在这里简要概述一些项目。你将看到 Arrow 在实际应用中的一些不同用例,包括以下内容:

  • 一个分布式 SQL 查询引擎 Dremio Sonar,我们在第 7 章《探索 Apache Arrow Flight RPC》中用它演示了 Arrow Flight
  • 使用 Spice AI 构建与本地数据集和/或机器学习模型联合的应用程序
  • 多个使用 Arrow JavaScript 库在浏览器中执行超快计算的项目
  • 一个基于 Arrow 构建的开源时序数据库 InfluxDB,它暴露了一个 Arrow Flight SQL 端点

使用 Dremio Sonar 探索数据

Arrow 的根源可以追溯到 Apache Drill 项目中的 ValueVector 对象,这是一个用于 Hadoop、NoSQL 和云存储的 SQL 查询引擎。Dremio Sonar 最初是从 Apache Drill 衍生而来的,Dremio 的一位创始人共同创建了 Arrow。Arrow 被 Dremio Sonar 用作其查询和计算引擎的内部内存表示,从而提升了其性能。自 Arrow 创立以来,Dremio 的工程师对 Arrow 项目做出了许多贡献,带来了显著的创新。首先,让我们来看看其架构及 Arrow 的角色。

澄清 Dremio Sonar 的架构

作为一个分布式查询引擎,Dremio Sonar 可以部署在许多不同的环境和场景中。然而,其核心架构相对简单,如图 10.1 所示。由于是分布式的,它可以通过增加协调器(Coordinator)和执行器(Executor)的数量来横向扩展,分别处理查询的规划和执行。集群协调由一个 Apache ZooKeeper 集群处理,协调器节点和所有执行器节点都与其通信。虽然协调器节点使用的元数据存储可以是本地的,但执行器需要共享一个分布式存储机制。执行器节点必须共享一个分布式存储位置,以确保原始数据、配置数据和缓存数据保持同步。

image.png

我提到过,Dremio Sonar 使用 Arrow 作为其内部数据计算的内存表示。由于 Dremio Sonar 可以连接到各种数据源,这意味着从物理存储位置检索到的任何原始数据都必须转换为 Arrow 格式。如果你的原始数据只是分布式存储服务(如 S3)中的 Parquet 文件,这个转换过程可以非常简单且高效,因为 Parquet 和 Arrow 之间的转换速度非常快。但是,如果你的原始数据存储在通过 ODBC 连接的 PostgreSQL 数据库中,转换可能会比较慢,因为 PostgreSQL 和 ODBC 在传输大量数据时经常成为瓶颈。

即使是最快的执行引擎,在处理超大数据集时,也无法突破物理限制来实现亚秒级响应时间。为了解决这个问题,Dremio Sonar 使用了一种称为 "reflections" 的技术。Reflection 是一种介于物化视图和索引之间的混合形式,它仅作为 Parquet 文件的集合存储。每个 reflection 可能会被分区和/或排序,以减少为查询服务所需读取的文件数量。即便如此,当目标是实现超快的查询时间时,即使将数据从 Parquet 转换为 Arrow(尽管经过高度优化),对于大量数据而言仍可能需要时间。为应对此类情况,Dremio Sonar 提供了将 reflection 数据存储为原始 Arrow IPC 格式的选项,允许将缓存的 Arrow 数据直接加载到内存中,而无需进行任何转换。这一切都转化为超快的查询执行和计算速度。

由于 Dremio 对 Arrow 的使用及其对社区的参与,其工程师为 Arrow 库做出了重要贡献。他们参与了 Arrow Flight 的创建和开发,Dremio 是第一个拥有 Arrow Flight 连接器的系统(相比 ODBC/JDBC 连接器,性能提升了 20 倍到 50 倍)。他们还深度参与了 Arrow Flight SQL 的开发,是提供最初官方实现的贡献者之一。然而,Dremio 做出的一个我们尚未在本书中提到的重要贡献是 Gandiva 项目。Gandiva 是 Arrow 的一个执行内核,为 Arrow 数据缓冲区的底层操作提供了巨大的性能提升。Gandiva 最初是为提高 Dremio Sonar 处理分析工作负载的性能而开发的,并于 2018 年被捐赠给了 Arrow 项目。

神圣的数据分析库

Gandiva 是源自印度史诗《摩诃婆罗多》中的一把神弓。在故事中,Gandiva 是不可摧毁的,从它射出的每一支箭都会变得强大千倍。考虑到该库基于 Arrow 的执行所提供的性能优势,这个名字显得非常贴切。

和许多大数据查询引擎一样,Dremio Sonar 是用 Java 实现的。在开发 Gandiva 之前,Dremio Sonar 执行 SQL 查询的过程是将查询动态编译为可以由 Java 虚拟机(JVM)执行的高效字节码格式。将查询动态编译为字节码表示,比简单地解释和评估 SQL 表达式带来了显著的性能提升。然而,Gandiva 更进一步,利用 LLVM 编译器的能力,对 Arrow 数据进行的许多低级内存操作(例如排序或过滤)可以即时生成高度优化的汇编代码,从而实现更好的资源利用率和更快的操作速度。

什么是 LLVM? LLVM 是一个开源项目,提供了一套编译器和工具链实用程序,最早开发于 2000 年左右。除了为多种编程语言提供通用编译器工具外,LLVM 还提供用于即时编译(JIT)的库。与在执行前将源代码编译为机器码不同,JIT 编译是在运行时进行的,它允许程序动态地将操作转换为机器码并执行这些代码。这种方法提供了额外的灵活性,同时仍能利用高度优化的编译代码的执行性能。Gandiva 正是利用了这一功能!

为了确保最快的执行速度,Gandiva 是一个 C++ 库,并通过 Java 原生接口(JNI)桥接 Java API 与 C++ 代码通信。由于 Dremio Sonar 是用 Java 编写的,因此它利用这些 API 来生成代码和评估表达式。图 10.2 展示了 Gandiva 库如何被利用的高层次表示:

image.png

使用 Gandiva 的基本步骤标注和列出如下:

  1. 首先,应用程序创建一个表达式树来表示其需要计算的操作。Gandiva 支持过滤和投影操作,以及各种数学、布尔和字符串操作。
  2. 表达式树被传递给 Gandiva 表达式编译器,后者返回一个包含已编译模块引用的对象。编译步骤还使用了预编译表达式的缓存,以确保速度快。
  3. 编译后的表达式可以传递给 Gandiva 执行内核,并传入一系列 Arrow 记录批作为输入进行操作,返回的结果也是 Arrow 记录批。

在撰写本文时,除了 C++ API 和 Java 绑定外,Gandiva 库还有 Python 绑定。希望随着时间的推移,社区会继续构建其他语言(如 Ruby、Go 等)的 Gandiva 绑定。随着 Gandiva 的持续采用,Arrow 的使用也在不断增长,这使得处理 Arrow 格式的数据更加高效。使用和嵌入 Gandiva 避免了应用程序需要重新发明轮子并自行实现这些工作。

除了分析管道和 SQL 评估,Arrow 在其他用例中也可以提供显著的优势。接下来我们将讨论如何利用 Arrow 作为基因组学数据处理中的序列比对/映射对象的内存数据表示。

优化数据工作流

在处理大型数据集的各类工程领域中,机器学习(ML)和人工智能(AI)工作流常常需要处理最大的数据集。然而,如果你的全职工作不是机器学习,并且没有专门的 ML 团队支持,创建一个能够学习和适应的应用程序通常非常困难。为了解决这个问题,一些工程师决定介入,帮助开发者更轻松地创建智能和自适应的应用程序。

Spice AI(spice.ai/)是一家风险资本支持的初创公司(撰写本文时),其目标是创建一个便携的运行时,能够在多个数据源之间进行数据联合和本地化。他们在 GitHub 上开源了一个产品,名为 Spice.ai(github.com/spiceai/spi…)。该产品使用 Apache Arrow 和 Arrow Flight SQL 进行通信和内部数据表示,使其能够轻松与 DuckDB 和 Apache DataFusion 等也使用 Arrow 的系统交互。

通常,将 AI/ML 集成到应用程序中的传统方法是将 AI/ML 服务作为应用程序的一个独立部分。一个专门的团队或管道(通常是数据科学家)从某处获取数据并在其上训练模型。在长时间的训练、调试、再训练的循环之后,模型作为一个服务被部署,应用程序通过与该服务交互,根据模型提供的结果获得答案或见解。

相比之下,Spice.ai 允许开发人员将 AI 引擎直接集成到应用程序中,而不是作为独立的基础设施模块,如图 10.3 所示:

image.png

在数据源与数据连接器/处理器以及这些处理器与 AI 引擎之间的不同通信层次中,Arrow 被用于提高效率。通过将通信和处理切换到使用 Arrow 和 Arrow Flight,Spice.ai 能够处理的数据集规模扩大了 10 倍到 100 倍。此外,正如我们在第 7 章《探索 Apache Arrow Flight RPC》中看到的那样,它还提升了大型数据集的传输时间。整合 Arrow 也使得 Spice.ai 能够与 InfluxDB、Snowflake 和 Google BigQuery 等数据源集成,作为 AI/ML 引擎的输入。基本上,任何提供 Arrow Flight 端点或 Arrow IPC 记录批流的数据源都可以与之集成。

对于任何时间敏感的数据,提升性能的最佳方法始终是将计算靠近数据源。这也是 Spice.ai 决定提供便携式容器运行时的理念。通过使用简单的 HTTP API 部署,使得它能够在任何应用程序旁边部署,无论是本地部署、公共云的一部分,还是部署在诸如手机等边缘设备上。他们还通过提供一个社区驱动的、可复用组件库来为 Spice.ai 运行时建立社区。Spice 的运行时还能作为一种数据库 CDN,通过本地物化数据并加速来自大量来源的查询数据。

现在,让我们更直观地理解这一点。历史上,在 Web 浏览器中提供更快、更高效的数据处理一直是一个极具挑战的任务。JavaScript 作为用于交互式网站的主要语言,其局限性使得这一任务尤为困难。作为一种解释性语言,JavaScript 对内存使用有显著限制(以保护用户),因此在处理数据方面并不理想。而这种处理对实现轻松的数据交互至关重要。因此,许多项目应运而生,旨在利用 Arrow 在浏览器中为此类用例提供支持,我们将在接下来的部分讨论这些项目。

在浏览器中使用 JavaScript 运行 Arrow

当前最常见的应用程序部署方式之一是开发 Web 应用程序,这样你就可以在同一位置为手机、平板电脑或笔记本/桌面浏览器提供应用程序。在构建现代交互式 Web 应用程序时,JavaScript 和/或 TypeScript 通常会参与其中。现在我们已经介绍了一些使用 Arrow 的服务和系统示例,接下来我们将讨论几个直接在浏览器中充分利用 Arrow 的项目。

Perspective:获得一些视角

Perspective 是一个可视化库,最初由 J.P. Morgan 开发,后来通过金融科技开源基金会(FINOS)以 Apache 开源许可证 2.0 开源。Perspective 是用 C++ 编写的,并为 WebAssembly 和 Python 进行了编译,同时提供了 JavaScript 组件来包装 WebAssembly 模块。Perspective 库导出了两个主要模块:一个数据引擎库和一个用户可配置的可视化 Web 组件(依赖于数据引擎)。

查看!
Perspective 的主页是 perspective.finos.org,你可以在这里找到文档以及 GitHub 仓库的链接。此外,它还包含了各种图像和视图,展示了可以用它实现的功能。

在浏览器中,Perspective 引擎作为 Web worker 运行,独立于主线程运行。当使用 Node.js 时,默认情况下引擎在进程内运行,而不是作为独立进程运行。两种情况下,库都导出基于 Promise 的接口(可以与 ES6 的 async/await 一起使用)。如果数据是只读的、静态的或由用户提供,那么使用 JavaScript 客户端库运行 WebAssembly 和 Web worker 并行渲染可以实现接近原生的性能。唯一的缺点是需要将整个数据集下载到客户端。图 10.4 显示了一个简单的示意图。Perspective 能够理解 Arrow 格式的数据、原始 CSV 数据或 JSON(面向行的对象)数据。

image.png

在处理较大的数据集、实时数据或多个用户同时访问的数据时,一种可行的替代方案是使用客户端/服务器模型。为此,服务器端可以选择 Node.js 或 Python,允许 JavaScript 客户端复制和同步服务器端的数据,如图 10.5 所示。通过利用 Arrow,数据可以在服务器和客户端之间非常高效地传输,同时仍然允许浏览器的用户界面渲染多种图表类型,从简单的柱状图到复杂的热力图或散点图不等。浏览器中的渲染将随着数据的更新而动态和交互式地更新视图,且这些更新将从服务器端流式传输到客户端。

image.png

如果你正在开发一个需要对数据集进行可视化的应用程序,我强烈建议你看看是否可以利用 Perspective 库和/或其组件!例如,Visual Studio Code 扩展 Data Preview(marketplace.visualstudio.com/items?itemN…)就是一个很好的例子。这个 Visual Studio Code IDE 的扩展利用了 Perspective 库,提供了一系列工具,用于导入、查看、切片和绘制多种对象或文件和数据格式的图表,正如我们之前在 Jupyter 小部件中看到的那样。你可能会用它构建出什么呢?

如果你觉得 Perspective 不太适合你的需求,可以试试另一个模块,名为 Falcon。

Falcon 的飞跃

与 Perspective 类似,Falcon 是一个用于在浏览器中进行交互式数据可视化分析的库。你可以在 github.com/vega/falcon 找到这个模块,并查看一系列展示其功能的演示。Falcon 的独特之处在于其可替换的引擎以及提供的众多小部件和组件,支持记录之间的交叉过滤。

对于最多 1000 万行的小型数据集,它拥有一个完全基于 Arrow 构建的引擎,完全在浏览器中运行。或者,如果你需要更高的性能,可以使用 DuckDB 的 WebAssembly SQL 数据库(github.com/duckdb/duck…),这是为浏览器构建的(它也是基于 Arrow 构建的!),作为 Falcon 的查询引擎。最后,Falcon 还可以连接到 Heavy.AI(前称 OmniSci)数据库,作为引擎使用(www.heavy.ai/product/hea…)。在你问之前,答案是肯定的——Heavy.AI 也支持 Arrow 用于数据摄取,并使用 Arrow 提供的库来利用 GPU 进行计算。

连接的涌入

InfluxDB 是一个针对时间序列数据优化的分析数据库,特别适用于大规模处理实时流数据。其背后的许多工程师也是 Arrow 库(特别是 Rust 实现)的核心贡献者,InfluxDB 广泛使用了这些实现。

InfluxDB 的核心是利用开放标准来提供抽象的数据共享层。它还提供了一个数据目录层,便于与 Iceberg 或 Delta Sharing 交互,允许直接访问 Parquet 文件,从而与其他基于云的分析、BI 或机器学习工作负载集成。由于 InfluxDB 基于 Apache DataFusion 项目开发,开发人员创造了“FDAP 堆栈”这一术语来描述 InfluxDB 3.0 的架构(Flight、DataFusion、Arrow 和 Parquet),如图 10.6 所示。

image.png

通过采用这一开源项目的技术堆栈,并拥抱它们使用的开放标准,InfluxDB 获得了诸多好处:

  1. 开发者专注于特定的时间序列功能:他们的开发者可以专注于实现所需的时间序列功能,而不必自己构建所有这些复杂的组件。
  2. 与其他系统的集成变得简单:由于 InfluxDB 提供了 FlightSQL 接口,他们无需自己构建客户端和驱动程序,而是可以直接利用现有的 FlightSQL 驱动程序,从而免费获得 ODBC、JDBC 和 ADBC 支持。使用者可以选择任何支持 Arrow 的语言(如 pandas、Spark、PyTorch 等)来连接 InfluxDB,而不需要复杂的数据转换和处理。
  3. 开源项目的协作优势:这些参与的开源项目都非常复杂,只有有限的开发者具备足够的技能和时间来进行贡献。通过开源模型,大量工程师可以协作解决复杂问题,所有人都能从中受益。
  4. 回馈社区:采用这些标准促使 InfluxDB 反哺社区。例如,InfluxData 为 Grafana 监控平台贡献了一个 FlightSQL 插件,允许 InfluxDB 作为数据源使用。因此,任何提供 FlightSQL 端点的系统都可以通过社区插件与 Grafana 集成,创建数据仪表板。

如果你对 InfluxDB 有兴趣,可以在 GitHub 上查看这个开源项目:github.com/influxdata/…

记住,本章只是简要介绍了几个有趣地利用 Arrow 的项目。如果有哪个项目吸引了你的兴趣,不妨去深入了解一下!

总结

无论你的数据是什么形式,只要你需要对数据进行任何处理或操作,检查是否能通过 Arrow 提高工作流效率都会有所帮助。在本章中,我们看到了使用 Apache Arrow 支持的关系数据库、分析引擎和可视化库。在每个例子中,Arrow 被用来实现更小的内存占用和更高效的资源利用。

各个行业都需要快速处理大量数据,无论是最新的科学研究还是制造业的指标。如果你从事数据处理工作,或许你也能在管道中的某个环节使用 Arrow。如果你不相信,可以查看官方 Apache Arrow 网站列出的由 Arrow 提供支持的项目:arrow.apache.org/powered_by/。你会发现本章提到的所有项目都在列表中,还有许多其他有趣的项目。我相信你能找到至少与你的用例相关的内容,或者至少激发你的好奇心。

如果你找不到现有的项目来利用 Arrow,或许你可以构建自己的项目并为 Arrow 贡献力量!这正是我们下一章的主题:如何在 Arrow 项目中留下你的印记。Arrow 的发展得益于其社区的支持和吸引力。像任何其他开源项目一样,它需要开发者的贡献来实现增长。或许你有某些功能需求,或者有尚未被考虑到的用例。如果你像我一样喜欢深入研究库的核心,并构建可供他人使用的工具,那么欢迎为 Arrow 库做出贡献。下一章将帮助你开始这一旅程!