Hudi1.0新特性


发布于 2025-01-13 / 42 阅读 / 0 评论 /
Hudi1.0版本新特性介绍

Hudi 1.0 的构想源于对 Hudi 成长历程的回顾。Hudi 最初旨在解决大规模数据摄取、增量数据处理和快速创建的问题,并需要与 Presto、Spark、Flink、Trino 等查询引擎集成。虽然这些引擎在列式数据查询方面表现出色,但它们的整合过程却充满挑战。

作为湖仓领域的先锋,Hudi 曾被称为事务性数据湖。在生态系统的早期阶段,Hudi 做出了一些保守的设计选择,例如为不同的查询引擎开发了各自的连接器。在过去的五年中,Hudi 社区蓬勃发展,同时也暴露出更多元化的需求,特别是在事务性操作、快速更新和删除功能方面。因此,Hudi 团队认识到有必要对现有设计进行深层次的重新思考和优化,以适应不断变化的数据处理需求。

1.Hudi发展方向

Hudi 1.0 的五大发展方向体现了对当前数据湖架构的深度思考和对未来规划的远见:

1.1.深度查询引擎集成

Hudi 将探索更深层次的查询引擎集成,减少对多个专属连接器的依赖。通过深入集成 Hudi 的多模索引(metadata table)功能,并结合如 Velox 等组件,以提升查询性能。

1.2.泛化的关系型数据模型

随着越来越多的引擎支持更全面的 SQL 语法,Hudi 将扩展其数据模型,以更好地适应关系型数据处理的需求,充分利用现代数据生态系统的优势。

1.3.有服务器和无服务器的架构配置

Hudi 将发展混合架构,结合有服务器和无服务器配置,以适应不同的数据处理需求。元数据服务将得到加强,以支持更高效的并发控制和商用元数据服务的集成。

1.4.非结构化数据支持

Hudi 将扩展对非结构化数据(如图像、视频)的支持,以避免数据湖的碎片化,并实现非结构化数据的索引更新和变更捕获功能,以适应 AI 等新兴技术的需求。

1.5.数据库表的自我管理能力提升

Hudi 将继续完善其数据湖表管理服务,包括数据入湖、表服务计划执行和运维工具。未来将增加更多功能,如反向流数据导入、快照管理、检验分析报告工具、跨区域备份和记录级别的 TTL 管理,以实现更全面的数据表管理。

2.Hudi-1.0版本新特性

Hudi-1.0版本包含以下新特性

2.1.LSM tree 时间线

Hudi 的时间线本质上是一个不可变的事务日志,记录了表上所有已完成交易的详细信息。这个事务日志通常会随着每次提交而线性增长。Hudi 0.x 版本维护了两个时间线:活跃时间线和归档时间线。活跃时间线用于快速检索最新信息,而归档时间线则用于满足特定场景下的历史数据查询需求。由于归档数据采用了不同的存储机制,访问这些数据的成本相对较高。

为了优化 Hudi 时间线的存储效率,我们考虑采用 LSM tree 这种经过验证的高效数据结构,它特别适合处理大规模写入操作。在 Hudi 1.0 中,我们重新实现了写入时间线的机制,现在每次写入都会记录其起始和结束时间。此外,我们将现有的线性存储结构转换成了 LSM tree 结构,这允许进行更高效的压缩操作。

LSM tree 的优势在于其树形分层结构,能够在顶层(如内存或缓存)快速检索信息,同时在更长的时间线上,可以通过压缩多个事务到更大的 parquet 文件中,来提高读取效率和存储效率。这种结构不仅优化了数据的快速访问,还提升了整体存储的紧凑性和性能。

接下来对 LSM Tree 时间线进行了一系列测试,其中包括模拟了 100 万次提交的交易日志。在无需加载所有元数据的情况下,仅访问开始时间和结束时间以及事务中涉及的文件,我们实现了在 367ms 内加载整个时间线。这一性能提升得益于将时间线数据存储为 parquet 文件,这不仅减少了读取所需的元数据量,还提供了更高的读取灵活性,从而显著提高了加载效率。

2.2.函数索引

Hudi 的当前版本已经集成了一个多模式索引子系统,支持文件索引、列统计和布隆过滤器等功能,且这些索引可以异步构建。在 Hudi 1.0 中,我们希望进一步增强多模式索引的通用性。受到数据库索引机制的启发,我们考虑了基于 R 树的空间索引和基于 Lucene 的搜索索引等高级功能。例如,PostgreSQL 能够在表达式上创建索引,这启发了我们实现函数索引的思路。函数索引的引入将使 Hudi 的索引功能更加灵活和强大,从而提高查询效率和处理复杂查询的能力。

函数索引的一个典型用例是在处理包含组织 ID 和时间戳的事件流数据时。通常,我们希望根据组织 ID 进行分区,然后进一步根据时间戳细分。例如,如果有1,000 个组织的一整年数据,我们可能会创建 365,000 个分区。然而,这种做法可能会导致数据倾斜和大量小文件的问题,这既影响了存储效率,也降低了查询性能。

为了解决这个问题,Hudi 1.0 引入了函数索引。我们仅根据组织 ID 进行物理分区,然后为时间戳定义一个函数,比如将 Unix 时间戳转换为小时,并记录每个小时的最大值和最小值。这样就可以在索引中实现高效的 data skipping,即跳过不需要扫描的数据,同时保持文件存储的高效率,无需进行更细粒度的物理分区。这种方法既保留了存储效率,又提供了更灵活的分区结构,优化了查询性能和数据管理。

函数索引的使用可以通过一个简单的示例来解释。在左侧的 SQL 示例中,使用 city 来分区,同时还有一个时间戳字段。我们不需要针对时间戳进一步分区,而是可以使用 create index 的语法来生成一个新的索引,该索引将时间戳转换为小时。一旦生成了这个索引,随后的 SELECT 语句就可以利用时间戳进行高效的数据跳过。

在右侧的两个 Spark DAG(有向无环图)演示中,展示了在有函数索引和没有函数索引的情况下,数据跳过是如何实现的。使用函数索引,Spark 查询可以更有效地跳过不相关数据,从而提高查询性能和减少资源消耗。这种索引策略不仅简化了数据分区,还提升了整体的数据处理效率。

2.3.新开发的文件组读取器和写入器

Hudi 自创建之初就设计了一个基于主键的概念。在 MOR(Merge-On-Read)表类型中,我们实现了一个合并操作,从第一天起就支持快照查询,即实时地将日志数据合并到基础文件中。这一机制确保了即使在数据不断写入和更新时,也能高效地执行查询操作,提供了对历史和最新数据的统一视图。

对于 Hudi 中的合并操作,我们发现了潜在的优化空间。具体来说,我们可以在记录日志的同时,记录下日志所需更新的基础文件的位置信息。这样,在进行合并操作时,能够直接定位到文件的具体位置,从而高效地执行合并。

2.4.部分更新(partial update)的优先支持

传统的日志文件默认记录整条更新语句,但在许多情况下,只需记录更新的字段。通过仅记录更新的字段及其值和位置,能够极大优化合并过程。

设计文件组读取器和写入器的另一个好处是,它使得与各种查询引擎的集成变得更加简便。这种设计统一了接口,使得扩展对不同引擎的支持变得更加方便,从而提升了 Hudi 的整体灵活性和可扩展性。

针对基于位置的合并操作,我们进行了一系列基准测试。这些测试涉及了两个不同规模的合并表,一个包含 500GB、7.5 亿条记录,另一个包含 1TB、15 亿条记录。每条记录大约 1KB 大小,表包含 1000 个分区,每个文件大约 256MB。

在测试中,我们首先批量加载数据,然后执行删除和更新操作,更新表中 50% 的记录。通过使用文件组读取器并利用位置信息进行合并操作,我们观察到了 12% 到 20% 的性能提升。这一提升随着数据量的增加而变得更加显著。对于具体的性能数据和详细分析,可以参考 Hudi 的 PR 10167。

在部分更新(partial update)的测试中,我们观察到了更为显著的性能提升。上图中展示了更新操作的结果对比。当使用全量更新,即整条记录的所有字段与仅更新部分字段时,更新的延迟降低了 1.4 倍。同时,写入的文件大小减少了 70 倍,这是因为我们节省了大量未更新的数据。由于写入更为高效,节省了空间,合并操作也变得更为高效,我们观察到了 5.7 倍的性能提升。这些优化不仅提高了更新操作的效率,还显著减少了存储空间的占用。

2.5.非阻塞并发控制

这个设计基于一个常见场景:一个每分钟写入的进程和每小时执行一次的 GDPR 删除作业。如果采用乐观锁机制,删除作业可能会频繁遇到冲突,因为删除操作是随机的,这会导致删除作业在大多数情况下都需要重试,从而浪费资源。

Hudi 从一开始就采用了 MVCC(多版本并发控制)机制。在写入侧,允许不加阻塞地写入,而在使用 MOR 模式时,在合并侧执行异步合并操作。此外,我们可以利用合并的机会进行类聚操作,以优化存储。这种设计确保了在处理并发写入和删除操作时,系统的效率和资源利用率得到提高。

为了解决多个写入器并发控制的问题,Hudi 支持乐观锁的使用,也引入了早期冲突检测机制。此外,Hudi 探索了更通用的非阻塞多版本并发控制机制。在 Hudi 1.0 中,我们实现了一个基于 MOR 写入过程的非阻塞并发控制。

当 MOR 写入操作在不同写入器上生成不同的日志文件时,Hudi 最初不会阻塞写入过程。通过使用全局单调递增的时间戳来记录每个写入的开始和结束时间,我们可以在合并或快照读取阶段解决潜在的冲突。这种方法允许在写入时保持非阻塞状态,从而提高了写入效率,同时确保了数据的最终一致性。