在花了大约 4 年时间致力于 Apache Hudi(其中包括 3 年Committer身份)之后,我决定开始这个博客系列(blog.datumagic.com),旨在以有组织且适合初学者的方式展示 Hudi 的设计和用法。我的目标是确保对分布式数据系统有一定了解的人能够轻松地理解该系列。该系列将包含 10 篇文章,每篇文章都会深入探讨 Hudi 的一个关键方面。(为什么是 10?纯粹是对 0 和 1 的有趣致敬,与系列标题相呼应:))最终目标是帮助读者从广度和深度上理解 Hudi,使他们能够自信地利用这个开源项目并为其做出贡献。在撰写本文时,Hudi 0.14.0 正处于候选版本阶段。因此,整个系列以及配套代码和示例都将基于此版本。
概述
Hudi 是一个事务性数据湖平台,它将数据库和数据仓库功能引入数据湖。下图摘自 Hudi 社区举办的网络研讨会[1],清楚地说明了该平台的主要功能
Hudi 的核心定义了一种表格式,用于组织存储系统内的数据和元数据文件,从而实现 ACID 事务、高效索引和增量处理等功能。本文的其余部分将探讨格式详细信息,主要展示存储上的 Hudi 表的结构并解释不同文件的角色。
存储格式
下图描述了 Hudi 表在存储中的基本路径下的典型数据布局。
有两种主要类型的文件:位于 .hoodie/
目录中的元数据文件,以及存储在分区路径中(如果表已分区)的数据文件,或者直接在基本路径(如果未分区)下的数据文件。
元数据
<base path>/.hoodie/hoodie.properties
文件包含基本的表配置,例如表名称和版本,表的写入端和读取端都将遵守和使用这些配置。
除了 hoodie.properties 之外,还有将事务操作记录到表中的元文件,形成 Hudi 表的时间轴。
代码语言:javascript复制# an example of deltacommit actions on Timeline
20230827233828740.deltacommit.requested
20230827233828740.deltacommit.inflight
20230827233828740.deltacommit
这些元文件遵循以下命名模式:
代码语言:javascript复制<action timestamp>.<action type>[.<action state>]
"action timestamp"
- • 标记第一次计划运行操作的时间。
- • 唯一标识时间轴上的操作。
- • 在时间轴上的不同操作之间单调递增。
"action type"显示该动作做出了什么样的改变。有一些写入操作类型,例如 commit 和 deltacommit,它们指示表上发生的新写入操作(插入、更新或删除)。此外,还有表服务操作,例如压缩和清理,以及恢复操作,例如保存点和恢复。我们将在以后的帖子中更详细地讨论不同的操作类型。
"action state"可以是“已请求”、“进行中”或“已完成”(没有后缀)。顾名思义,“已请求”表示正在计划运行,“正在执行”表示正在进行中,“已完成”表示操作已完成。
这些操作的元文件采用 JSON 或 AVRO 格式,包含有关应应用于表或已应用的更改的信息。保留这些事务日志可以重新创建表的状态,实现快照隔离,并通过并发控制机制协调写入器冲突。
.hoodie/
下还存储有其他元数据文件和目录。举一些例子,元数据包含与时间轴上的操作相关的更多元数据,并充当写入端和读取端的索引。 .heartbeat/
目录存储用于心跳管理的文件,而 .aux/
则保留用于各种辅助目的。
数据
Hudi将物理数据文件分为Base File(基本文件)和Log File(日志文件):
- • 基本文件包含 Hudi 表中的主要存储记录,并针对读取进行了优化。
- • 日志文件包含其关联基本文件之上的记录更改,并针对写入进行了优化。
在 Hudi 表的分区路径中(如前面的布局图所示),单个基本文件及其关联的日志文件(可以没有或多个)被分组在一起作为文件切片。多个文件切片构成一个文件组。文件组和文件切片都是逻辑概念,旨在封装物理文件,从而简化读取端和写入端的访问和操作。通过定义这些模型,Hudi 可以
- • 满足读写效率要求。通常基本文件配置为列式文件格式(例如 Apache Parquet),日志文件设置为基于行的文件格式(例如 Apache Avro)。
- • 实现跨提交操作的版本控制。每个文件切片都与时间轴上操作的特定时间戳相关联,文件组中的文件切片本质上跟踪所包含的记录如何随时间演变。
可以快速查看此处[2]的 Hudi 表示例,了解数据布局。
表格类型
Hudi 定义了两种表类型 - 写入时复制 (CoW) 和读取时合并 (MoR)。布局差异如下:与 MoR 相比,CoW 没有日志文件,并且写入操作会导致 .commit
操作而不是 .deltacommit
。在我们的讨论中,我们一直以 MoR 为例。一旦掌握了 MoR,理解 CoW 就变得简单了 - 可以将 CoW 视为 MoR 的特殊情况,其中基本文件中的记录和更改在每次写入操作期间隐式合并到新的基本文件中。可以在此处[3]浏览示例 CoW 表。
在为 Hudi 表选择表类型时,考虑读取和写入模式非常重要,因为这会产生一些影响:
- • 由于每次写入都会重写新文件切片中的记录,因此 CoW 具有较高的写入放大,而读取操作始终会得到优化。这非常适合读取繁重的分析工作负载或小型表。
- • MoR 的写入放大较低,因为更改会“缓冲”在日志文件中,并进行批处理以合并和创建新的文件切片。但是,读取延迟会受到影响,因为读取最新记录需要将日志文件与基本文件进行实时合并。
用户还可以选择仅读取 MoR 表的基本文件,以提高效率,同时牺牲结果的新鲜度。我们将在接下来的帖子中详细讨论 Hudi 的不同阅读模式。随着 Hudi 项目的发展,与从 MoR 表读取相关的合并成本在过去的版本中得到了优化。可以预见 MoR 将成为大多数工作负载场景的首选表类型。
回顾
在零到一系列的第一篇文章中,我们探讨了 Hudi 存储格式的基本概念,以阐明元数据和数据在 Hudi 表中的结构。我们还简要解释了不同的表类型及其权衡。如概览图所示,Hudi 作为一个综合性 Lakehouse 平台,提供不同维度的功能。在接下来的九篇文章中,我将逐步介绍该平台的其他重要方面。
引用链接
[1]
Hudi 社区举办的网络研讨会: [https://www.youtube.com/live/rOOXajfRZ_w?si=VesAn95eyIc_z2DM&t=549](https://www.youtube.com/live/rOOXajfRZ_w?si=VesAn95eyIc_z2DM&t=549)
[2]
此处: [https://github.com/xushiyan/apachehudi-from0to1/tree/main/post-01/example-MoR-table](https://github.com/xushiyan/apachehudi-from0to1/tree/main/post-01/example-MoR-table)
[3]
此处: [https://github.com/xushiyan/apachehudi-from0to1/tree/main/post-01/example-CoW-table](https://github.com/xushiyan/apachehudi-from0to1/tree/main/post-01/example-CoW-table)