Hudi 数据湖快速入门

Hudi 是给湖上的表装一颗数据库的心脏——主键、索引、事务、一条流水账,让"改一条、删一条、只取新的"在对象存储上变成普通操作。Uber 2016 年为"改一条记录"造的轮子,2024 年 1.0 升格湖仓管理系统,2025 年 1.1 把引擎做成可插拔。本文从那条改不完的记录出发,拆到你能动手建表为止。

文本版 · 供搜索与朗读

Hudi 数据湖快速入门

数据仓库系列 · 第九篇 · Apache Hudi · 快速入门

Hudi 数据湖快速入门

Hudi 是给湖上的表装一颗数据库的心脏——主键、索引、事务、一条流水账,让"改一条、删一条、只取新的"在对象存储上变成普通操作。Uber 2016 年为"改一条记录"造的轮子,2024 年 1.0 升格湖仓管理系统,2025 年 1.1 把引擎做成可插拔。本文从那条改不完的记录出发,拆到你能动手建表为止。

目 录

一分钟版本
一条改不完的记录
时间线:表的流水账
纸怎么叠:文件组与文件切片
两种改法:重印,还是夹便签
索引:主键住在哪里
增量拉取:只喝新到的牛奶
上手:十分钟一张 Hudi 表
1.0 与 1.1:从表格式到"数据库体验"
选型:什么时候轮到 Hudi
回到那条记录
参考

0. 一分钟版本

Hudi 读作 hoodie。它解决的问题只有一个:湖上的文件不会改字。它给对象存储上的表加上主键、索引、事务和一条流水账,让"改一条、删一条、只取新的"变成一次普通提交。

四个概念撑起全部:时间线(表的流水账)、文件组与文件切片(活页纸)、COW/MOR(重印 vs 夹便签)、索引(卡片目录)。

2024 年 12 月的 1.0 把自己升格为"数据湖仓管理系统":记录级索引、二级索引、非阻塞并发。2025 年 11 月的 1.1 把存储引擎做成可插拔,能用 Hudi 的索引和写入服务去维护 Iceberg 元数据。

选型一句话:2026 年的通用默认是 Iceberg;Hudi 的甜点区是高频 CDC upsert 与分钟级增量链路(第七篇口径),1.1 之后"二选一"开始松动。

1. 一条改不完的记录

凌晨两点,一张行程表。乘客打来电话:昨天那单计价错了,要改。这条记录住在一个 Parquet 文件里,那个文件里躺着几亿行、几个 GB。Parquet 是不可变的——写完那一刻就定了型,像印好的书页。

你手里有三个选项。重写整个文件,为了一个错字重印一本书;重写整个分区,重印一排书架;或者把修正记在别处、等明天的批量作业一起算——那今晚的报表就还是错的。

这本来不该是难题。数据库几十年前就把"改一条"做成了家常便饭:UPDATE,一毫秒,完事。问题出在场景换了:数据库装不下的量,湖装得下;湖却没有数据库那套器官——主键、索引、事务、日志。

2016 年,Uber 的数据团队天天撞这堵墙。行程数据会迟到、会被修正、会因审计要求被删除,每次都回头改旧分区。他们给自己造了个内部工具,代号 Hoodie——卫衣。2017 年 3 月开源,2019 年 1 月进 Apache 孵化器,2020 年 6 月毕业成顶级项目。顺带纠一个流传很广的写法:中文资料常把进孵化器写成 2018 年,ASF 毕业公告和 Uber 提交博客这两个一手口径都写 2019 年 1 月,采信后者。按 Uber 提交给 Apache 时的账,当时它管着 4000 多张表、数 PB 数据,把一批管道的产出延迟从"数小时"压进 30 分钟。

本系列第五篇《拉链表实现》讲过 Hive 时代对这个问题的民间偏方:全量切片加链式区间。拉链表是没有更新能力时对更新的模仿。Hudi 的起点就一句话:别模仿了,把更新本身给湖装上。

2. 时间线:表的流水账

先给你看 Hudi 里最重要的东西。不是数据文件,是一个目录。

每张 Hudi 表的根下有个 .hoodie 目录,里面躺着一条按时间排好的流水账,官方叫它时间线(Timeline)。表上发生的每件事——一次写入提交、一次压缩、一次清理、一次回滚——都是账上的一行,带时间戳,这样的一行叫一个 instant。

把顺序倒过来理解更好懂:在 Hudi 眼里,一张表首先是一串事件的账;一堆文件的现状,只是把账重放到此刻的结果。想看上个月的样子?把账重放到上个月。昨晚手滑了?回滚也是账上的一笔新账,不是撕账页。

这个类推要盯紧一点,不然会跑偏:账上的行是操作,不是数据。数据本身在下一节的活页纸里,时间线只记"谁在几点对表动了什么手术"。

这条账撑起了你在数据库里熟悉的那些词:提交原子生效(ACID 的 A 和 I),读者只见完整快照不见半成品(快照隔离),翻回任意时刻(time travel),出错回滚(rollback),还能打存档点(savepoint)防误删。

1.0 版把这条账升级了两下,都值得知道。一是每个动作从"一个时刻"变成"一段区间"(requested 到 completion),并发动作的先后关系从此可以精确裁决;二是账本本身改用 LSM 树组织。官方给过一个基准:一张表攒到约 1000 万个动作——相当于每 30 秒提交一次、连提十年——扫完 8.4 GB 元数据用 162 秒。账厚到这个程度还翻得动,"分钟级提交一直跑"这件事才有底气。

3. 纸怎么叠:文件组与文件切片

数据这一侧的组织只有三层,比听起来简单。

分区下面(比如 city=上海/),文件按文件组(file group)组织。一个文件组有固定的 file id,终身负责同一群主键——可以想成一叠活页纸:纸的编号不变,内容可以出新版。每一版叫一个文件切片(file slice),由两部分组成:基础文件(Parquet,列式,读得快)和日志文件(行式追加,写得快)。基础文件加上它之后的便签,才是这个切片的完整内容。

graph TD
T[Hudi 表 trips] --> P0[分区 city=上海]
T --> P1[分区 city=北京]
P0 --> FG1[文件组 fg-A 固定 file id]
P0 --> FG2[文件组 fg-B]
FG1 --> S1[切片1 基础文件 v1]
FG1 --> S2[切片2 基础文件 v2 加日志 v2]

那"一条记录住哪"怎么找?靠索引——一本卡片目录:主键对应文件组。没有目录,每次 upsert 都得全表扫一遍问"谁见过这个主键";有目录,直奔那一叠纸。索引是 Hudi 跟朴素表格式拉开差距的地方,第 5 节专门讲。

4. 两种改法:重印,还是夹便签

现在把"改一条记录"落到纸上。Hudi 给了两种表型,差别全在"改动什么时候付账"。

抄写型(Copy on Write,COW):upsert 到来,找到涉及的文件组,当场把基础文件重写一份新版(旧版本留在时间线里)。写入贵,读取白给——文件永远是干净的 Parquet,任何引擎直接查,零合并成本。

便签型(Merge on Read,MOR):upsert 到来,不动基础文件,往日志文件里追加一条便签。写入白给,读取付账——快照查询要把基础文件和便签当场合并;也可以选"读优化查询",只读基础文件、跳过便签,快,但可能读到旧值。

便签不能无限贴。定期有个后台作业把便签誊进正文、产出新的干净基础文件,这叫 compaction——中文叫压缩,压掉的不是字节,是版本账。誊写节奏你自己定:要新鲜就勤誊,要省事就攒着。

从表里这四行读:写入做多少、读取做多少、文件长什么样、谁替你收拾。

COW 抄写型
MOR 便签型

写入时
重写整个受影响的基础文件
往日志文件追加一条

读取时
零合并,直接读 Parquet
快照查询当场合并;读优化查询跳过便签但可能读旧值

文件形态
只有 Parquet
Parquet 加日志文件

后台功课
基本不用
compaction 定期誊写

graph LR
U[一次 upsert] --> COW[COW 重写基础文件出新版]
U --> MOR[MOR 追加一条日志]
MOR --> CMP[compaction 定期誊写]
COW --> Q[快照查询 直接读 Parquet]
CMP --> Q
MOR --> RO[读优化查询 只读基础文件]

选择规则短得不值一节:读多改少、要被各种引擎直接查,COW;写入重、更新频繁、要分钟级新鲜度,MOR。CDC 入湖这类改个不停的活,基本都落在 MOR 上。

一句简化契约:上面把日志说成便签、compaction 说成誊写,主干如此。真实系统里日志格式、合并策略(1.0 起叫 merge mode,默认按事件时间排序、最新的赢)、调度触发都是可配置的细节,入门阶段可以先当它们不存在。

5. 索引:主键住在哪里

回到 upsert 的第一步:找到那条记录。这一步的快慢,几乎决定整张表的写入快慢。

0.x 时代的家底有两条。布隆过滤器索引(bloom index):给每个基础文件配一张"概率卡",先问文件"可能藏着这个主键吗",可能才打开看;文件多了,问一圈也不便宜。桶索引(bucket index):按主键哈希把记录直接钉进固定的桶,省掉问路。

1.0 的主菜是记录级索引(record index):每个主键到底住在哪个文件组,记进表的元数据表里,点查变成 O(1) 的"查卡片",不再扫描。配套还有二级索引——给非主键列也建目录(比如按手机号找人)。官方在 10 TB 的 TPC-DS 数据集(约 72 亿行)上给的数据是:二级索引把查询延迟降了 88% 到 95%。对湖上表来说这是换挡:这类点查以前要么搬到专门的数据库,要么忍着。

索引为什么是这颗"数据库心脏"里最硬的一块?因为 upsert 等于定位加写入,索引管定位。没有索引的表格式也能靠 MERGE INTO 改数据,但每次都要全表对账;有了记录级索引,改动直达那叠纸。Hudi 十年做的很多事,归拢到底就是一本越做越像数据库的卡片目录。

6. 增量拉取:只喝新到的牛奶

Hudi 名字里那个 I(Incremental)是它的独门。2017 年的开源博客把名字展开成 Hadoop Upsert Delete and Incremental——三个词,upsert 打头,incremental 收尾,正好对应本文的第 1 节和这一节。

场景:下游表要跟着上游表变。老办法是每天全量重算,数据量翻十倍、成本翻十倍,新鲜度还是天级。Hudi 的办法藏在时间线里:账本天然记着"哪笔提交发生在几点之后",下游就能只拉某个 instant 之后的变动记录——Spark SQL 里是 hudi_table_changes('trips', 'latest_state', beginTime),DataSource API 里是 incremental 查询。上游改了 0.1%,下游就处理 0.1%。

Uber 提交 Apache 时给的效果账:数百条这样的增量管道,延迟从小时级压进 30 分钟。这句还有个背景值得知道:2017 年博客把目标场景定义为"能容忍约 10 分钟延迟的统一服务层"——当时夹在"纯流式 1 分钟"和"批处理数小时"之间、没人管的地带。

跟第五篇拉链表划一下界,免得两个"增量"打架:拉链表管"维度历史怎么记"(属性级),增量拉取管"下游怎么只吃变化"(管道级)。一个管账本的内容,一个管账本的传阅。CDC 也顺带说清:把表打开 cdc.enabled 后可以用 'cdc' 查询类型拿"改了什么"的变化流(目前限 COW 表),跟"全表此刻长什么样"是两种读法。

7. 上手:十分钟一张 Hudi 表

以下命令以 1.1 系列为底本(1.1.0 于 2025 年 11 月发布,12 月出补丁 1.1.1)——我写这篇时能从官方发布页核到的最新稳定线就是它。启动 spark-sql 时挂上扩展和 catalog,三个 conf 名字长,抄就行:

spark-sql \
--packages org.apache.hudi:hudi-spark4.0-bundle_2.13:1.1.1 \
--conf spark.sql.extensions=org.apache.spark.sql.hudi.HoodieSparkSessionExtension \
--conf spark.sql.catalog.spark_catalog=org.apache.spark.sql.hudi.catalog.HoodieCatalog \
--conf spark.serializer=org.apache.spark.serializer.KryoSerializer

建表。主键、表型、排序字段全在 TBLPROPERTIES 里,一行不留悬念:

CREATE TABLE trips (
ts BIGINT, uuid STRING, rider STRING,
driver STRING, fare DOUBLE, city STRING
) USING HUDI
TBLPROPERTIES (type='mor', primaryKey='uuid', orderingFields='ts')
PARTITIONED BY (city);

写入即 upsert,有则改、无则插——这就是 2016 年 Uber 要的那个动词:

INSERT INTO trips VALUES (1695115998, '334e26e9', 'rider-A', 'driver-K', 19.1, '上海');

改价、删单、按源表合并,全是标准 SQL 的长相:

UPDATE trips SET fare = 25.0 WHERE rider = 'rider-A';
DELETE FROM trips WHERE uuid = '334e26e9';

MERGE INTO trips AS t
USING fare_adjustments AS s
ON t.uuid = s.uuid
WHEN MATCHED THEN UPDATE SET t.fare = t.fare + s.delta
WHEN NOT MATCHED THEN INSERT *;

时间旅行,把表翻回某天,审计和"昨晚到底谁改的"都靠它:

SELECT * FROM trips TIMESTAMP AS OF '2026-09-08';

增量,只看某时刻之后的变动:

SELECT * FROM hudi_table_changes('trips', 'latest_state', 'earliest');

引擎侧没有锁:Spark、Flink(1.1 起支持 Flink 2.0)、Presto、Trino、Hive 都能读写,非 JVM 生态还有 Rust 实现的 hudi-rs(带 Python 绑定)。Flink 写入在 1.1 换成了引擎原生数据结构,官方基准里 5 亿行的写入吞吐从 6.7 万条/秒提到 23.5 万条/秒。

8. 1.0 与 1.1:从表格式到"数据库体验"

2024 年 12 月 16 日,Hudi 发 1.0,官宣博客的口气很直白:表格式只是冰山一角,湖上真正缺的是数据库体验——于是 Hudi 自我升格为数据湖仓管理系统(DLMS)。

三个大件。前面讲过的记录级与二级索引是一件;时间线区间化加 LSM 化是一件;第三件是非阻塞并发控制(NBCC)——多个写入者和 compaction 同时动一张表,不再互相失败,官方口径是 MVCC 加 TrueTime 语义(RFC-66)。另有一个对写放大直接开刀的部分更新:官方基准是 1 TB 的 MOR 表、80% 随机更新、只改 100 列里的 3 列,更新延迟改善 91.4 倍、写出字节省 70.2 倍、查询延迟改善 5.7 倍。列式表"改一格重写一片"的老毛病,被日志格式的字段级支持正面接住了。

2025 年 11 月 25 日的 1.1(800 多个 commit)做了件更有性格的事:把存储引擎做成可插拔。新的 HoodieTableFormat 接口把时间线、事务、索引、并发这些核心从文件格式里剥出来,默认仍是原生 Hudi 格式;Iceberg 已有 Apache XTable 适配器可接——用 Hudi 的记录级索引高效写,适配器同时维护一份 Iceberg 元数据,给只认 Iceberg 的 catalog 和引擎读;Delta 官方说法是"铺路"中。顺手还把 clustering 提了一档:Parquet 二进制直拷,100 GB 从 18 分钟到 1.2 分钟,算力从 28.7 降到 1.3 task-hour。

这件事放回第七篇的格局里读才有味道:Iceberg 已是事实标准,战场移到 catalog 层。Hudi 1.1 等于承认了这一点,然后把竞争从"你用谁的格式"改成"你用谁的引擎"——格式我也能写,索引和 upsert 还是我强。这一手到 2026 年 9 月还没有终局答案,但方向摆出来了。

9. 选型:什么时候轮到 Hudi

第七篇给过三格式速查。Hudi 1.1 之后,下面这四行里有两行要加注脚:

场景
第七篇口径
1.1 之后的注脚

通用新建湖仓
Iceberg
不变;Hudi 引擎写 Iceberg 元数据的路已通(XTable 适配)

高频 CDC upsert 存量
Hudi
甜点区没变,金融、互联网存量最深

Flink 实时链路
Paimon
Paimon 仍首选;Hudi 的 Flink 2.0 支持补齐了代差

Databricks 全家桶
Delta
与 Hudi 无关

反模式四条,都是第七篇反模式清单的 Hudi 特化版:

上 MOR 不配 compaction 策略。便签贴满墙,快照查询越来越慢,直到某天爆掉。誊写节奏是设计参数,不是默认值。

把增量拉取当 CDC 用。latest_state 给的是"最新状态",要"变化本身"(改前改后)得开 CDC 查询。

time travel 当无限保留。快照不过期,元数据和存储双爆炸,第七篇同一条:定 expiration。

以为上了表格式就等于上了口径管理。格式管事务不管语义——schema-on-read 的解释权问题第七篇讲透了,这里不重复。

10. 回到那条记录

凌晨两点那通电话,现在有了新接法:MOR 表上一次 upsert,走记录级索引定位到那叠纸,往日志里夹一条便签,时间线上记一笔,几分钟内下游增量管道把修正带走。没有人重印任何一本书。

Hudi 十年做的事,拆开就是一句话:把数据库的器官——主键、索引、事务、日志——一件一件移植给湖上的文件。有的器官(行级更新、增量消费)它第一个造出来;有的战场(格式标准)它后来让给了 Iceberg,然后转身去当引擎。

你要动手的话,入口就在第 7 节那几条命令,加官方 quick-start。就这么回事。

参考

ASF 毕业公告(2020-06-04):起源时间线、毕业时采用者(Alibaba、Tencent、EMIS Health、Uber 等)、EMR 5.28 起内置

Uber 工程博客:Hoodie 开源(2017-03-11,名字展开义、增量处理动机、约 10 分钟延迟的统一服务层定位);Uber Submits Hudi to Apache(4000 多张表、数小时压进 30 分钟)

Apache Hudi 官方文档:Overview(引擎矩阵、hudi-rs、COW/MOR、snapshot/incremental/read-optimized 查询)、Quick-Start Guide(本文全部 SQL 语法的出处)

Apache Hudi 官方博客:1.0 发布(2024-12-16,DLMS、NBCC、索引与部分更新基准);1.1 发布(2025-11-25,可插拔格式、XTable、clustering 与 Flink 性能数据)

本系列第七篇《数据湖与湖仓一体》(五宗罪、三格式格局、catalog 战场)、第五篇《拉链表实现》(属性级历史的正解)

深度研究 · 编译:智柴 · 2026-09-10 · 源文件:Hudi数据湖快速入门_2026-09-10.md

#Hudi #数据湖 #湖仓一体 #表格式 #智柴

暂无表态

想参与讨论或点赞?登录后使用完整功能

讨论回复(0)

暂无回复,登录后可参与讨论
合作

智谱 GLM-5 已上线

在智谱开放平台 BigModel.cn 打造 AI 应用。新一代旗舰模型 GLM-5 在推理、代码、智能体综合能力达到开源模型 SOTA。

领取 2000万 Tokens