Hudi 技术栈
Hudi 将核心的数据仓库与数据库功能直接引入数据湖(近年来更常被称为数据湖仓架构),使其从一堆对象/文件升级为管理良好的表。Hudi 通过表格式层在 Apache Parquet/ORC 等开放文件格式之上提供表抽象,针对频繁写入、基于表快照的大规模查询以及高效增量扫描进行了优化。要理解 Hudi 技术栈,我们可以简单地将其中的组件对应到经典论文《Architecture of a Database System》(数据库系统架构),只是采用了更现代的名称。
在此基础之上,Hudi 引入了许多数据库中都具备的存储引擎功能(论文中称为"事务性存储管理器"),从而提供并发控制、索引、变更捕获以及更新/删除等事务能力。存储引擎还包含管理与维护表所必需的表服务,这些服务与底层存储层紧密集成,并由上层写入者或平台组件(例如独立的表管理服务)自动执行。
随后,Hudi 定义了清晰的读写 API,便于各种 SQL 引擎以及使用各自流行数据处理框架编写的多种编程语言代码与表进行交互。Hudi 还附带若干平台服务,可用于性能调优、表运维、表监控、数据摄入、数据导入/导出等。

Hudi 技术栈
因此,综合来看,Hudi 技术栈已不再仅仅是一种"表格式",而是扩展为一个全面且稳健的数据湖仓平台。本节将探讨 Hudi 技术栈,并拆解构成 Hudi 的各层软件组件。标有星号(*)的功能代表正在进行中的工作,虚线框则表示计划中的未来工作。这些组件共同致力于实现该项目的愿景。
湖存储
存储层是数据文件/对象(例如 Parquet)以及所有表格式元数据的存放之处。Hudi 通过云原生方式和 Hadoop FileSystem API 与存储层进行交互,从而兼容包括用于快速追加的 HDFS,以及多种云存储(如 Amazon S3、Google Cloud Storage (GCS) 和 Azure Blob Storage)在内的各类系统。此外,Hudi 还提供了自己的存储 API,可以依赖与 Hadoop 无关的文件系统实现,以简化各种文件系统的集成。Hudi 引入了一个自定义的包装文件系统,为更优的存储优化奠定基础。
文件格式

Hudi 中的文件格式结构
文件格式承载实际数据,并物理存储在湖存储中。Hudi 基于 File Group 和 File Slice 这样的逻辑结构进行操作,它们由 Base File 和 Log Files 组成。Log Files 在 Base Files 中已存储的记录之上记录更新/删除/插入操作,并且会定期将日志文件压缩为少量日志文件(log compaction)或 base 文件(compaction)。未来的更新目标是集成非结构化数据(例如 JSON、图片)等多样化的格式,并实现与事件流、OLAP 引擎和数据仓库中不同存储层的兼容。Hudi 的布局方案将对 Log File 的所有变更编码为一系列数据块(data、delete、rollback)。通过以开放的文件格式(如 Parquet/Avro)提供数据,Hudi 使用户能够为特定工作负载接入任意计算引擎。
原生表格式

Hudi 的原生表格式
与文件格式相类比,表格式仅涉及文件在表中的分布方式、分区方案、schema 以及跟踪变更的元数据。Hudi 将表或分区内的文件组织成 File Group。更新会被记录到与这些 File Group 关联的日志文件中,从而确保高效的合并。与 Hudi 表格式相关的主要组件有三个。
- Timeline:Hudi 的时间线存储在
/.hoodie/timeline文件夹中,是一个关键的事件日志,按顺序记录表中的所有操作,事件会保留一段指定的时间。Hudi 独特地将每个 File Group 设计为自包含的日志,使得即使历史操作已被归档,也能通过增量日志(delta log)重建记录状态。这种方式能根据表的活动频率有效限制元数据的大小,对于管理更新频繁的表至关重要。 - File Group 与 File Slice:在每个分区内,数据以 Base 文件和 Log 文件的形式进行物理存储,并组织为逻辑概念 File group 和 File Slice。File group 包含多个版本的 File Slice,并被划分为多个 File Slice。一个 File Slice 由 Base 文件和 Log 文件组成。文件组中的每个 File Slice 都通过创建其 base 文件或第一个日志文件的那次写入来唯一标识,这有助于对 File Slice 进行排序。
- 元数据表(Metadata Table):元数据表以另一张读时合并(merge-on-read)的 Hudi 表的形式实现,能够以较低的写放大高效处理快速更新。它采用了基于 SSTable 的文件格式,以便进行快速的索引键查找,存储文件路径、列统计信息和 schema 等关键信息。这种方式通过减少对昂贵的云文件列表操作的需求,简化了各项操作。
Hudi 将更新记录到 Log Files 的方式,比 Hive ACID 这类需要将所有增量记录与所有 Base 文件进行合并的系统更加高效,合并开销也更低。有关 Hudi 中各种表类型的更多信息,请参阅表类型文档。
可插拔表格式
从 Hudi 1.1 开始,Hudi 引入了可插拔的表格式框架,将 Hudi 强大的存储引擎能力从其原生格式扩展到 Apache Iceberg 和 Delta Lake 等其他表格式。该框架将 Hudi 的核心能力——事务管理、索引、并发控制和表服务——与数据文件所使用的具体存储格式解耦。

可插拔表格式
Hudi 提供原生格式支持(默认通过 hoodie.table.format=native 配置),而 Apache XTable(孵化中) 则为 Iceberg 和 Delta Lake 等格式提供可插拔的格式适配器。该框架使组织能够针对每种用例选择合适的格式,同时保持统一的运维体验,并在所有格式上充分利用 Hudi 强大的存储引擎。例如,你可以借助 Hudi 的记录级索引能力高效地向 Hudi 表写入高频更新,同时通过 Iceberg 适配器维护 Iceberg 元数据,该适配器支持多种目录(catalog)以便进行读取。这种架构避免了厂商锁定,并拥抱开放的湖仓一体(lakehouse)生态。
存储引擎
Hudi 的存储层由核心组件构成,这些组件负责基础的操作和服务,使 Hudi 能够在数据湖仓存储上高效地存储、检索和管理数据。这一功能与 PostgreSQL、MySQL、MongoDB、Cassandra 和 Clickhouse 等流行数据库中存储引擎所扮演的角色相当。
索引

Hudi 中的索引
Hudi 中的索引能够优化查询规划,减少 I/O,加快响应速度,并以较低的合并成本实现更快的写入。元数据表充当额外的索引系统,为读取方和写入方普遍带来索引的收益。计算引擎可以利用元数据表中的各类索引(如文件列表、列统计信息、布隆过滤器、记录级索引以及表达式索引),快速生成优化的查询计划并提升读取性能。除元数据表索引外,Hudi 还支持基于简单连接(join)的索引、存储在基础文件页脚中的布隆过滤器、HBase 等外部键值存储,以及分桶(bucketing)等优化存储技术,从而高效定位包含特定记录键的文件组(File Group)。Hudi 还提供表达式索引和二级索引等读取端索引,以提升读取性能。Hudi 会有意识地利用表的分区方案来实现全局和非全局索引策略,从而将记录唯一性的作用范围限定在给定分区内或跨所有分区的全局范围内。
表服务

Hudi 中的表服务
Hudi 提供了多种表服务,用于保持表存储布局和元数据管理的高性能。Hudi 内置了这些表服务,支持以内联、半异步或全异步模式运行。此外,Spark 和 Flink 流式写入器可以以连续模式运行,并以异步方式调用表服务,智能地与写入器共享底层执行器。下面我们来逐一了解这些服务。
聚簇(Clustering)
聚簇服务类似于云数据仓库中的功能,允许用户使用排序键对频繁查询的记录进行分组,或将较小的 Base File 合并为较大的文件,以实现最优的文件大小管理。它与其他时间线操作(如清理和压缩)完全集成,从而能够进行智能优化,例如避免对正在聚簇的 File Group 执行压缩,进而节省 I/O 开销。
压缩(Compaction)
Hudi 的压缩服务提供日期分区、I/O 限制等策略,将 Base File 与增量日志合并以生成更新后的 Base File。借助 Hudi 的文件分组和灵活的日志合并机制,它允许对同一 File Group 进行并发写入。这使得即使在并发更新记录的情况下,删除操作也能以非阻塞方式执行。
清理(Cleaning)
清理器服务基于时间线增量执行,会移除超过已配置的增量查询保留期限的 File Slice,同时为长时间运行的批处理作业(例如 Hive ETL)留出足够的运行时间。这样用户就能回收存储空间,从而节省成本。
索引(Indexing)
Hudi 可扩展的元数据表包含了表的辅助数据。该子系统涵盖多种索引,包括 files、column_stats 和 bloom_filters,便于高效地定位记录和跳过数据。如何在写入吞吐量与索引更新之间取得平衡是一项根本性挑战,因为传统的索引方法(例如在索引期间锁定写入)处理时间过长,对于大型表并不实用。Hudi 通过其创新的异步元数据索引解决了这一问题,能够在不阻碍写入的情况下创建各种索引。这种方法不仅提升了写入延迟,还通过减少写入与索引活动之间的竞争,最大限度地降低了资源浪费。
并发控制
并发控制定义了不同的写入器/读取器/表服务如何协调对表的访问。Hudi 使用单调递增的时间来对表状态的各种变更进行排序和定位。与数据库类似,Hudi 采用清晰区分写入器(负责插入更新/删除)、表服务(专注于存储优化和记账)以及读取器(用于查询执行)的方式。Hudi 提供快照隔离,在这些不同操作之间提供一致的表视图。它在写入器与表服务之间、以及不同的表服务之间采用无锁的非阻塞 MVCC 实现并发,并对多写入器使用带早期冲突检测的乐观并发控制(OCC)。从 Hudi 1.0 开始,引入了非阻塞并发控制(NBCC),允许多个写入器通过非阻塞的冲突解决方式并发操作表。
湖缓存*

Hudi 中提出的 Lake Cache
当今的数据湖在快速写入数据与最优查询性能之间面临权衡。写入较小的文件或记录增量日志可以提升写入速度,但卓越的查询性能通常需要打开更少的文件并进行预物化合并。大多数数据库使用缓冲池来降低存储访问成本。Hudi 的设计支持创建一个多租户缓存层,用于存储预合并的文件切片(File Slice),随后可以利用 Hudi 的时间线(timeline)来简单地传递缓存策略。传统上,缓存位于查询引擎附近或内存文件系统中。将缓存层与 Hudi 的事务性存储集成,可以实现跨查询引擎的共享缓存,支持更新和删除,并降低成本。其目标是借助社区其他成员的贡献,构建一个与所有主流引擎兼容的湖上缓冲池。
编程 API
写入器
Hudi 表可以用作 Spark/Flink 管道的 sink,与原生 Parquet/Avro sink 的文件写入相比,Hudi 的写入路径提供了多项增强能力。它将写入操作分为增量操作(insert、upsert、delete)和批处理/批量操作(insert_overwrite、delete_partition、bulk_insert),并赋予各自特定的功能。upsert 和 delete 能高效合并具有相同键的记录,并与文件大小调整机制协同工作;而 insert 操作则智能地跳过预合并等某些步骤,从而保留管道的性能收益。类似地,bulk_insert 操作可用于在数据导入时控制文件大小。批处理操作集成了 MVCC(多版本并发控制),实现增量处理与批处理之间的无缝切换。此外,写入流程还包含多项优化,例如通过 RocksDB 处理大规模合并以及并发 I/O,从而提升写入性能。
读取器
Hudi 为写入者和读取者提供快照隔离,使得在主流查询引擎(Spark、Hive、Flink、Presto、Trino、Impala)以及云数仓中都能进行一致的表快照查询。它通过采用轻量级流程来优化查询性能,尤其是在读取基础列式文件时,并集成了各引擎专属的向量化读取器,例如 Presto 和 Trino 中的实现。这种可扩展的模型免去了为每个引擎单独维护读取器的需要,同时充分利用各引擎的独特优化,如 Presto 和 Trino 的数据/元数据缓存。对于需要合并基础文件(Base Files)和日志文件(Log Files)的查询,Hudi 采用可溢写映射(spillable maps)、延迟读取等机制来提升合并性能。此外,Hudi 还提供读优化(read-optimized)查询选项,以牺牲数据新鲜度换取更快的查询速度。近期还新增了一些特性,如位置合并(positional merge)、将部分日志文件仅编码为发生变更的列,以及支持将 Parquet 用作日志文件格式,以改善 MoR 快照查询性能。
用户访问
SQL 引擎
Hudi 兼容多种 SQL 查询引擎,能够满足各类分析需求。在分布式 ETL 批处理场景中,Apache Spark 因其对大规模数据的高效处理能力而被广泛使用。在流式处理场景中,将 Apache Flink、Apache Spark 的 Structured Streaming 等计算引擎与 Hudi 结合使用,可获得强大的支持。此外,Hudi 还支持 Trino、Presto 等现代数据湖查询引擎,以及 ClickHouse、StarRocks 等现代分析型数据库。这种对计算引擎的多样化支持,使 Hudi 成为一个灵活、可适配各类用例的平台。
代码框架
虽然 SQL 在数据工程领域依然占据主导地位,但另一种同样重要且广泛使用的数据工程/数据科学实践,是使用 Java、Scala、Python、R 等不同语言编写代码,借助语言的完整表达能力,利用复杂算法来分析数据。为此,Hudi 支持多种流行的数据处理框架,如 Apache Spark 和 Apache Flink,也支持基于 Python 的分布式框架(如 Daft、Ray),并提供 Rust 原生绑定,便于与 C/C++ 编写的引擎集成。
...
平台服务

Hudi 中的各种平台服务
平台服务提供面向数据和工作负载的特定功能,它们直接位于表服务之上,与写入器和读取器进行交互。像 Hudi Streamer(及其 Flink 对应组件)这样的服务,专门用于处理数据和工作负载,可与 Kafka 流以及各种格式无缝集成,从而构建数据湖。它们支持自动检查点管理、与主流 schema registry(包括 Confluent)的集成以及数据去重等功能。Hudi Streamer 还提供回填(backfill)、一次性运行以及配合 Spark/Flink 流式写入器的连续模式运行等功能。此外,Hudi 还提供了用于 Hudi 表快照导出和增量导出的工具、新表导入工具,以及用于分析或工作流管理的提交后回调,从而增强生产级增量管道的部署能力。除上述服务外,Hudi 还广泛支持各类 catalog,例如 Hive Metastore、AWS Glue、Google BigQuery、DataHub 等,可将 Hudi 表同步至这些 catalog,以便由 Trino、Presto 等交互式引擎进行查询。
Metaserver*

Hudi 中提出的 Metaserver
将表元数据存储在湖存储上虽然具备可扩展性,但与通过 RPC 访问可扩展的元服务器相比效率较低。Hudi 通过其元服务器(称为 "metaserver")来解决这一问题,为管理大量表的表元数据提供了一种高效的选择。目前,内嵌于 Hudi 写入进程中的时间线服务器使用本地 RocksDB 存储和 Javalin REST API 来提供文件列表服务,从而减少对云存储的列举(listing)操作。自 0.6.0 版本以来,独立时间线服务器的发展趋势日益明显,其目标是实现水平扩展并提升安全性。这些进展将为未来需求构建出更高效的湖 元存储(metastore)。
评论
登录后参与评论
KnowForge