前面整理 Hadoop、Hive、Spark、Flink 这些组件时,更多关注的是计算引擎怎么跑、任务怎么提交、SQL 怎么查询。真正把数据长期沉淀到 HDFS 或对象存储之后,还会遇到另一个问题:文件系统只负责存文件,并不天然知道一张表的更新、删除、事务、增量消费和文件版本应该怎么管理。
普通 Hive 表或直接写 Parquet 文件,在纯追加场景下很好用。每天写一个分区,下游按天查询,逻辑简单,成本也低。但是一旦数据来自 MySQL binlog、Kafka 事件流、业务状态表或者需要修正历史分区,问题就会变复杂:同一条记录可能重复到达,历史记录可能要更新,错误数据可能要删除,下游任务又希望只消费上一次之后的变化。
Apache Hudi 主要解决的就是这类问题。它不是一个新的计算引擎,也不是简单替代 Parquet 的文件格式,而是在 HDFS、S3 这类存储系统之上,为数据湖表补上一层事务、索引、文件版本、表服务和增量读取能力。本文先不急着安装和写代码,先把 Hudi 放到整个大数据链路中看清楚:它到底解决什么问题,和 Spark、Flink、Hive、Trino 这些组件是什么关系。
本文只解释 Hudi 的定位、核心能力和适用场景,不展开具体安装命令。Hadoop、Hive、Spark、Flink、Kafka、MySQL 与 Hudi 的实际部署、依赖冲突和截图,会放到后续环境安装文章中记录。
1. 背景与定位
1.1 背景问题
传统离线数仓里,Hive 表通常按日期分区存放:
/warehouse/ods/order_info/dt=2025-06-03/part-00000.parquet
/warehouse/ods/order_info/dt=2025-06-04/part-00000.parquet
如果业务只是每天离线导入一次订单快照,或者只追加日志明细,这种方式没什么问题。Spark、Hive、Presto、Trino 都能直接读取 Parquet 文件,压缩率和查询性能也不错。
麻烦通常出现在三个场景。
第一个场景是 CDC 入湖。MySQL 里的订单状态会从 CREATED 变成 PAID,再变成 SHIPPED。如果每次变更都直接追加到 Hive 表里,下游查询最新订单状态时就要自己去重、排序、取最新版本。一旦多个任务对“最新”的理解不一致,数据口径就会乱。
第二个场景是 历史数据修复。比如某一天的用户维表字段解析错了,只修正几万条记录。普通 Parquet 文件不能原地更新,常见做法是重算整个分区,或者写一份修正表再在查询时关联。数据量小的时候还能忍,表大以后就会变成明显的计算和存储浪费。
第三个场景是 增量 Pipeline。下游任务并不想每天全量扫描一张大表,只想知道从上一次运行之后,哪些记录发生了变化。普通 Hive 表可以通过分区或业务时间字段做近似增量,但这不是表自身提供的变更日志,下游很难统一处理乱序、补数、回滚和删除。
所以数据湖真正需要的不只是“能存很多文件”,还需要下面这些表级能力:
| 能力 | 普通文件表的问题 | Hudi 的处理方向 |
|---|---|---|
| 更新 | Parquet 文件不能原地更新 | 通过 FileGroup 和新版本文件表达更新 |
| 删除 | 删除通常要重写分区 | 支持 delete 操作,并通过新的 commit 表达记录在新表版本中不可见 |
| 事务 | 文件写到一半时缺少统一可见性 | 通过 Timeline 管理 Instant 状态 |
| 增量读取 | 依赖业务字段或分区约定 | 通过 commit time 读取变化 |
| 小文件治理 | 需要外部任务合并 | 表服务负责 Compaction、Clustering、Clean |
| 记录定位 | 更新前不知道记录在哪个文件 | 通过 Index 降低定位成本 |
| 表结构变化 | 字段增删容易和历史文件不一致 | 通过 Schema Evolution 兼容表结构演进 |
这也是 Hudi 出现的背景:让数据湖不只是便宜的大文件仓库,而是能支撑持续变更的数据湖表。
1.2 Hudi 的定位
Hudi 的全称是 Hadoop Upserts Deletes and Incrementals。这个名字基本把它的核心能力写出来了:在 Hadoop 生态或类似分布式存储上,支持 Upsert、Delete 和 Incremental Query。
从类别上看,Hudi、Iceberg、Delta Lake 都属于开放数据湖表格式 / 湖仓表管理方向,只是 Hudi 更早围绕 upsert、delete 和 incremental processing 展开。
如果用一句话概括,我更愿意把 Hudi 理解成:
Hudi 是运行在开放存储之上的数据湖表管理系统,它用 Timeline、Index、FileGroup 和表服务,把一堆数据文件组织成可以更新、删除、回放和增量消费的表。
这个定位很重要。Hudi 不是 Spark 的一个普通插件,也不是 Flink 的一个 sink,更不是 Hive 的一种存储格式。Spark、Flink、Hive、Trino 这些组件分别从不同角度使用 Hudi 表:
| 组件 | 在 Hudi 链路中的常见作用 |
|---|---|
| Spark | 批量写入、离线初始化、SQL DML、历史修复、增量查询 |
| Flink | 实时写入、CDC 入湖、流式读取、长时间运行任务 |
| Hive | 元数据管理、离线查询、和已有数仓体系打通 |
| Trino / Presto | 交互式查询、BI 查询、联邦查询 |
| Kafka | 作为 CDC 或事件流中间层 |
| HDFS / S3 | 存放 Hudi 表的 Base File、Log File 和 Timeline 元数据 |
Hudi 站在这些组件中间,既不替代计算引擎,也不替代底层存储。它更像是数据湖上的表层协议:告诉写入端怎么提交变更,告诉读取端什么版本的数据可见,告诉运维任务什么时候压缩、清理和重排文件。
1.2.1 Hudi 的发展脉络
刚开始看 Hudi 时,我不太建议死记版本历史,但它的发展脉络能解释一个问题:为什么 Hudi 一开始就特别强调 Upsert、Delete 和 Incremental Query。
| 时间 | 关键变化 | 判断 |
|---|---|---|
| 2015 年前后 | 增量处理思想逐渐成型 | 大数据任务不能一直靠全量重跑解决问题 |
| 2016 年 | Uber 内部开始围绕大规模数据湖更新构建 Hudi | 业务数据库数据入湖后,更新和删除是刚需 |
| 2017 年 | Hudi 开源 | 记录级更新、增量消费不再只是单家公司内部问题 |
| 2019 年 | 进入 Apache 孵化器 | 生态集成开始成为重点,Spark、Hive、Presto 等引擎都需要对接 |
| 2020 年 | 成为 Apache 顶级项目 | Hudi 从“一个写入工具”变成更完整的数据湖表项目 |
| 2021 年以后 | Flink、SQL DML、元数据表、索引和表服务持续增强 | 实时入湖、湖仓查询和生产治理能力变得更重要 |
从这个过程可以看出,Hudi 并不是先有一个文件格式,然后再想办法接入计算引擎。它更像是从真实数据湖痛点里长出来的表管理系统:先解决更新、删除和增量,再逐步补齐 SQL、Flink、Metadata、Clustering、Catalog 等生态能力。
1.3 Hudi 在数据湖架构中的位置
下面这张图是本文对 Hudi 位置的理解。

这张图主要说明 Hudi 位于存储层和计算 / 查询引擎之间,补齐普通文件系统缺少的表级管理语义。
如果把外部组件再展开一层,Hudi 在数据湖链路里的关系大致如下:

这张架构图主要用于说明 Apache Hudi 在湖仓架构中的位置。整体来看,Hudi 位于底层开放存储层之上、计算查询层之下,承担的是“数据湖表管理层”的角色。它不是传统数据库,也不是单纯的文件格式,而是在 HDFS、S3、OSS 等开放存储之上,为数据湖中的 Parquet、ORC、Log File 等文件提供表级管理能力。
在数据进入湖仓之后,业务库 CDC、Kafka 事件流、日志文件、离线文件等数据会先落到开放存储层。Hudi 在这些文件之上提供 Record Key、Index、Timeline、File Group、File Slice 等机制,使数据湖支持 Upsert、Delete、增量读取、事务提交、版本管理和文件生命周期管理。这样,原本只是“文件堆积”的数据湖,就具备了接近数据库表的管理能力。
在 Hudi 之上,Spark、Flink、Hive、Trino、Presto 等计算和查询引擎可以对 Hudi 表进行写入、处理和查询。其中 Spark 常用于批处理和离线 SQL,Flink 常用于实时写入和 CDC 入湖,Hive 适合离线查询,Trino / Presto 适合交互式分析。Hudi Streamer 则可以用于将 Kafka、DFS 等数据源持续写入 Hudi 表。
上层的数据开发 IDE、调度系统、数据质量、元数据检索、数据报表、运营分析、风控推荐和数据服务 API,都可以基于 Hudi 管理后的数据展开。同时,右侧的平台治理与运维能力用于保障湖仓系统稳定运行,包括 Catalog / Schema 管理、权限控制、监控告警、数据质量校验、表服务治理和成本管理。整体来说,Hudi 的核心价值就是在开放数据湖之上提供可靠的表管理能力,让数据湖能够更好地支撑实时入湖、增量处理和上层分析应用。
2. 核心能力与使用场景
2.1 Hudi 与普通 Hive / Parquet 表的区别
普通 Hive / Parquet 表和 Hudi 表都可以存放在 HDFS 上,也都可以被 SQL 引擎读取。区别在于:普通表更多是文件集合,Hudi 表则有自己的 Timeline、索引、文件版本和表服务。
| 对比点 | 普通 Hive / Parquet 表 | Apache Hudi 表 |
|---|---|---|
| 表的本质 | 更像开放存储目录里的文件集合,表语义主要依赖目录、分区和 Metastore。 | 在开放存储之上增加表管理层,把文件组织成可更新、可回放、可增量消费的湖仓表。 |
| 写入语义 | 以 insert、追加文件、覆盖分区为主;记录级更新通常要靠外部逻辑处理。 |
支持 upsert、delete、insert_overwrite 等表级写操作,能围绕 record key 管理变化。 |
| 事务可见性 | 写入失败、部分文件可见、历史版本判断通常依赖计算引擎提交协议或外部约束。 | 通过 Timeline / Instant 管理提交状态,查询侧基于已完成 instant 构造一致视图。 |
| 记录定位 | 更新前不知道 record key 在哪个文件里,常见做法是扫描、重算分区或依赖外部索引。 | 通过 Index 辅助定位 record key 对应的候选 FileGroup,降低更新定位成本。 |
| 文件版本 | 文件之间缺少统一的表级版本关系,旧文件什么时候仍然有效不够明确。 | 通过 FileGroup / FileSlice 管理 Base File、Log File 和不同时间点的文件视图。 |
| 增量消费 | 通常只能按分区、业务时间或文件时间做近似增量,容易漏读或重复读。 | 可以围绕 commit / deltacommit 做 Incremental Query,更适合下游持续消费变化。 |
| 文件治理 | 小文件合并、历史文件清理、分区重算多由外部任务完成。 | 通过 Compaction、Clean、Clustering、Metadata Table 等表服务持续维护文件布局。 |
这个表主要强调:普通文件表缺少 Timeline、Index、FileGroup / FileSlice 和表服务等表级能力。
可以从几个角度看这个差异。
2.1.1 写入语义不同
普通 Hive 表通常以追加或覆盖分区为主:
INSERT INTO TABLE ods_order PARTITION (dt = '2025-06-03')
SELECT *
FROM tmp_order_delta;
如果要修复一个分区,常见写法是 INSERT OVERWRITE:
INSERT OVERWRITE TABLE ods_order PARTITION (dt = '2025-06-03')
SELECT *
FROM tmp_order_rebuild;
这种方式能解决问题,但它的粒度是分区,不是单条记录。哪怕只改几千条数据,也可能要重写一个很大的分区。
Hudi 更关注记录级别的变更。写入时通常会指定 record key、partition path 和 precombine 字段:
val hudiOptions = Map(
"hoodie.table.name" -> "ods_order_hudi",
"hoodie.datasource.write.recordkey.field" -> "order_id",
"hoodie.datasource.write.partitionpath.field" -> "dt",
"hoodie.datasource.write.precombine.field" -> "update_time",
"hoodie.datasource.write.operation" -> "upsert",
"hoodie.datasource.write.table.type" -> "COPY_ON_WRITE"
)
这几个字段决定了 Hudi 如何识别一条记录、如何分区、同一个 key 多次到达时谁更新、以及本次写入使用什么操作语义。
2.1.2 事务可见性不同
普通文件写入最怕写到一半。比如 Spark 任务已经生成了一部分 Parquet 文件,但任务失败了,下游如果直接扫描目录,就可能读到不完整数据。Hive 和 Spark 自身有一些提交协议可以降低这个问题,但表级历史、回滚、增量读取和后台服务状态并不是普通文件表天然具备的。
Hudi 通过 Timeline 管理表上的动作。一次写入、一次 compaction、一次 clean、一次 rollback,都会在 Timeline 上形成 Instant。Instant 通常会经历这些状态:
REQUESTED -> INFLIGHT -> COMPLETED
在正常的 Snapshot / Read Optimized 等查询视图下,查询侧主要基于 Timeline 中已经完成的 Instant 构造可见表状态,避免读到未完成写入。也就是说,下游读的不是“目录里碰巧有什么文件”,而是“Timeline 上已经完成的表版本”。这个差异是理解 Hudi 的关键。
2.1.3 增量消费方式不同
普通 Hive 表要做增量,常见做法是按分区读取:
SELECT *
FROM ods_order
WHERE dt >= '2025-06-03';
这种方式简单,但它只能表达分区级增量。如果一条历史分区里的记录在今天被修正,单纯按 dt 过滤可能读不到。如果按 update_time 过滤,又要依赖业务字段质量。
Hudi 的 Incremental Query 是围绕 commit time 展开的。下游任务可以记录上一次消费到的 commit,然后继续读取之后的变化。工程上通常会记录上一次消费到的 instant / commit,并在下一次任务中以 begin instant、end instant 或类似参数继续读取后续变化。这种方式更适合增量 Pipeline。
2.2 Hudi 的核心能力
这一节只做概念总览,后面的文章会逐篇拆开。
2.2.1 Upsert
Upsert 是 insert 和 update 的组合。新 key 到来时插入,已有 key 到来时更新。对于 CDC 入湖来说,这几乎是最核心的能力。
例如订单表里 order_id = 1001 先到达一条创建记录,后面又到达一条支付记录。普通追加表会保留两行;Hudi 表可以根据 order_id 和 update_time 合并出最新状态。
2.2.2 Delete
数据湖里做删除并不容易,因为底层文件通常不可变。Hudi 的删除不是简单调用文件系统删除某一行,而是通过新的提交表达“这条记录在新版本中不可见”。在 COW 表中可能表现为重写 Base File,在 MOR 表中可能表现为写入 Log File,最终由查询和 compaction 合并语义。
这类能力对于 GDPR 删除、业务撤单、脏数据清理都很重要。
2.2.3 Incremental Query
Hudi 的增量查询面向的是“表发生了哪些变化”,而不是“某个分区有哪些文件”。这让下游任务可以把 Hudi 表当成增量数据源使用:
上游 CDC -> Hudi 表 -> 下游宽表 / 搜索索引 / ClickHouse / 其他数据湖表
如果没有增量查询,下游经常只能全量扫描大表,或者自己维护复杂的水位线。
2.2.4 Transaction
Hudi 通过 Timeline 管理事务提交。写入任务生成数据文件后,还需要提交 Instant。查询侧根据完成的 Instant 构造表视图,从而避免读到未完成写入。
这不等于 Hudi 变成了关系型数据库。它仍然运行在分布式文件系统上,事务模型和 MySQL 这类数据库不同。但对于数据湖表来说,原子提交、快照读取和回滚能力已经能解决很多生产问题。
2.2.5 Index
更新一条记录之前,系统必须判断这条记录可能落在哪些文件里。普通数据湖没有这层映射,只能靠扫描或外部索引。Hudi 的 Index 负责帮助写入端判断某个 HoodieKey 可能落在哪些 FileGroup 中,从而减少更新定位时的扫描成本。
不同索引适合不同场景:
| 索引 | 常见场景 |
|---|---|
| Bloom Index | Spark 写入、非全局更新、通用场景 |
| Simple Index | 小表或测试场景,逻辑直接但成本较高 |
| HBase Index | 外部索引,适合随机查询但有额外运维成本;这类外部索引有额外组件依赖,入门和单机测试环境通常不会优先选择 |
| Flink State Index | Flink 实时写入中保存 key 到文件组的映射 |
| Bucket Index | 通过哈希分桶降低状态和定位成本 |
索引不是越强越好。全局索引能处理跨分区更新,但成本也更高。大表通常要先判断记录是否会跨分区变化,再决定是否使用全局索引。
2.2.6 Compaction
Merge On Read 表为了降低写入延迟,会先把更新写到 Log File。查询时如果每次都合并 Base File 和很多 Log File,读性能会下降。Compaction 的作用就是把 Log File 中的变更合并回新的 Base File。
可以把 MOR 理解成一种延迟整理策略:优先降低写入延迟,后面再通过 compaction 把文件整理成更适合查询的形态。
2.2.7 Clustering
Compaction 主要解决 MOR 表中 Log File 和 Base File 的合并问题,Clustering 更关注文件布局优化。比如数据写入时比较乱,后续可以按某些字段重新排序、合并小文件、优化查询过滤效率。
对于大表来说,Clustering 不是锦上添花。分区内小文件太多、文件排序混乱、查询经常扫描大量无关数据时,Clustering 会直接影响查询成本。
2.2.8 Metadata Table
大表上直接列目录成本很高,尤其是在对象存储上。Hudi Metadata Table 用来加速文件列表、列统计、索引等元数据访问。后面写到生产调优时会专门展开。
2.2.9 Schema Evolution
数据湖表不是一次建好后就永远不变。业务表经常会新增字段、调整可选字段,CDC 链路里还可能遇到上游 DDL 变化。如果只是一堆普通 Parquet 文件,历史文件和新文件的 Schema 不一致时,读写端很容易出现字段缺失、类型不兼容或查询结果不稳定的问题。
Hudi 支持以兼容方式处理表结构演进。这里先记住一个原则:Schema Evolution 不是让我们随意改字段类型,而是让数据湖表在可控范围内面对业务字段变化。后面写 Spark、Flink 和 Hive 集成时,还会看到它和 Avro Schema、Hive Metastore、Flink SQL 类型映射之间的关系。
2.3 典型使用场景
Hudi 不是所有数据湖表的默认答案。判断是否需要 Hudi,可以先看数据是否持续变化、是否需要增量消费、是否需要记录级更新。
2.3.1 CDC 入湖
这是 Hudi 最典型的场景。MySQL binlog 通过 Flink CDC 或 Kafka 进入实时链路,最终写入 Hudi 表。Hudi 负责根据主键做 upsert,下游查询得到最新状态。
一个简化链路如下:
MySQL binlog -> Kafka -> Flink SQL -> Hudi 表 -> Hive / Spark / Trino 查询
这个场景最关注四件事:
- 主键是否稳定。
- 事件顺序如何处理。
- 删除事件如何表达。
- 下游能不能按 commit 增量消费。
2.3.2 近实时分析
如果业务希望几分钟内看到最新数据,但又不想把所有分析都放到实时数据库里,Hudi MOR 表是一种选择。Flink 持续写入 Log File,查询侧通过 Snapshot Query 看到近实时结果,后台 compaction 定期整理文件。
这种方案不是没有代价。MOR 的读写平衡依赖 compaction 策略,如果压缩不及时,查询会被 Log File 拖慢;如果压缩太频繁,写入和资源成本又会上升。
2.3.3 增量 Pipeline
很多数据任务并不是面向最终用户查询,而是作为中间层继续加工。比如 ODS 层订单表变化后,下游 DWD、DWS、搜索索引、ClickHouse 明细表都需要同步变化。Hudi 的 Incremental Query 可以减少全量扫描。
这类场景里,Hudi 表既是存储结果,也是变更源。
在这里还要注意一个很容易混淆的点:业务事件时间和数据到达时间不一定相同。比如一条订单变更发生在 10:00,但因为网络、任务重启或 Kafka 堆积,真正写入 Hudi 的时间可能是 10:08。增量消费如果只看业务时间,容易漏掉迟到数据;Hudi 的 Timeline 则让下游可以围绕提交时间继续消费变化。后面讲 Timeline 时会专门拆这个问题。
2.3.4 数据修复和分区重算
离线数仓经常遇到历史分区修复。Hudi 支持 insert_overwrite、delete_partition、replace commit 等能力,可以用更明确的表语义表达修复过程。相比直接删除目录再重跑,Timeline 中有更清晰的提交记录。
这些能力在不同 Hudi 版本和不同引擎入口中的参数或语法可能不同,后续以实测命令为准。
2.3.5 增量导出
还有一类场景是把数据湖里的变化继续导出到外部系统,比如 Elasticsearch、ClickHouse、Redis 缓存或另一个湖表。传统做法可能会让下游直接消费 Kafka,但 Kafka 更适合保存一段时间内的事件流,不适合承载长期历史状态。
Hudi 表可以同时保存最新状态和提交历史,下游通过 Incremental Query 拉取上一次 commit 之后的变化,再写到目标系统。这样做的好处是:如果下游任务失败,可以从已记录的 commit 继续恢复,而不是重新扫全表,也不是完全依赖 Kafka 保留时间。
生产环境中还需要配合 cleaner / archive 保留策略,避免下游任务停太久后需要的增量提交历史已经被清理或归档。
2.4 版本选择先说明
| 组件 | 计划版本 | 选择原因 |
|---|---|---|
| Hadoop | 3.4.1 | 已经是比较新的 Hadoop 3.x,适合作为 HDFS / YARN 测试底座 |
| Hive | 4.0.x | 主要用于验证新版 Metastore 和查询链路,具体兼容性以后续安装实测结果为准 |
| Spark | 3.5.x | Hudi 1.0.x 支持 Spark 3.3、3.4、3.5 等 Spark 3.x 组合,其中 Spark 3.5.x 是默认 / 优先验证方向;实际安装时需要选择匹配 Spark 版本和 Scala 版本的 Hudi bundle |
| Flink | 1.20.x | Hudi 1.0.x 提供 Flink 1.20 支持,是实时写入测试的优先组合;实际安装时需要使用与 Flink 1.20 匹配的 Hudi Flink bundle / profile,不要混用旧版 Flink connector |
| Hudi | 1.0.2 | 使用 1.0.2 |
| Kafka | 3.8.x / 3.9.x | 用于 Kafka Source、DeltaStreamer 和 CDC 链路测试 |
| MySQL | 8.0.x | 用作 Hive Metastore 存储和 CDC 测试源 |
| JDK | OpenJDK 11 | 先用 OpenJDK 11 作为单机测试环境的保守选择,减少 Hadoop、Spark、Flink、Hive 混合部署时的兼容变量;JDK 17 后续单独验证 |
这里先只给方向。第 2 篇会在服务器上真实安装并验证,最终以能跑通 HDFS、YARN、Hive、Spark、Flink、Kafka、MySQL 和 Hudi 表读写为准。如果某个组件组合实际不兼容,会在安装文章里记录降级原因、报错现象和修复方式。后续安装文章中也会记录 Hive 查询 Hudi 表时使用的 bundle、classpath 和 metastore sync 参数。
3. 理解模型与常见误解
3.1 一个最小理解模型
刚开始学 Hudi 时,不建议先陷进几十个参数里。可以先记住下面这个模型:
Hudi 表 = 数据文件 + Timeline + Index + 表服务
其中:
- 数据文件负责存储真实数据,通常是 Base File 和 Log File。
- Timeline 负责记录表上发生过的动作。
- Index 负责定位记录在哪个 FileGroup。
- 表服务负责 compaction、clean、clustering、metadata 等后台治理。
如果一张表只有数据文件,它更像普通 Hive 表。如果一张表同时有 Timeline、Index 和表服务,它才开始具备 Hudi 的核心能力。
这个模型也能解释为什么 Hudi 不是单纯的文件格式。Parquet 只回答“数据怎么编码存储”,Hudi 回答的是“这张表如何持续变化,并且让读写双方看到一致的表状态”。
3.2 常见误解
3.2.1 Hudi 是不是只能配合 Spark 使用
不是。Spark 是 Hudi 很常用的批处理入口,尤其适合初始化、SQL DML、历史修复和离线写入。但 Hudi 同时支持 Flink 写入和读取,也可以和 Hive、Trino、Presto 等查询引擎配合。
这个系列里,Spark 和 Flink 都会写。Spark 更偏批处理,Flink 更偏实时入湖。
3.2.2 Hudi 是不是会替代 Hive
不会。Hive 可以继续作为 Metastore 和查询入口。Hudi 表可以同步到 Hive Metastore,让 Hive 或其他兼容 Metastore 的引擎看到表结构。对很多现有数仓来说,Hudi 更像是增强底层表能力,而不是推翻上层查询体系。
3.2.3 Hudi 表是不是一定比普通 Parquet 表快
不一定。Hudi 的优势是更新、删除、事务和增量能力,不是所有查询都天然更快。如果一张表永远只追加、从不更新、也没有增量消费需求,普通 Parquet 表可能更简单。
Hudi 会引入 Timeline、索引、元数据、compaction 等额外复杂度。只有当这些能力能解决真实问题时,引入 Hudi 才值得。
3.2.4 选 COW 还是 MOR
简单理解:
- Copy On Write 更适合读多写少、查询稳定的场景。
- Merge On Read 更适合写入频繁、希望降低写延迟的场景。
但这只是第一层判断。实际还要看更新比例、查询延迟要求、compaction 资源、下游引擎支持情况。第 5 篇会专门讲 COW、MOR 和三种查询方式。
4. 引入判断与总结
4.1 引入 Hudi 的判断
Hudi 的核心价值,不是“又多了一种大数据组件”,而是它把数据湖里长期靠约定和调度脚本维持的东西,收敛成了表自身的能力。
以前在 Hive 表上做更新,常见做法是:先找受影响分区,重跑临时表,覆盖目标分区,再让下游重新消费。这个流程能跑,但很多状态散在脚本、调度系统、临时目录和业务约定里。谁覆盖了哪个分区,哪些文件应该被清理,下游从哪里继续读,这些问题并没有统一落在表本身。
Hudi 的做法是把这些变化写进 Timeline,把记录定位交给 Index,把文件版本交给 FileGroup 和 FileSlice,把清理、压缩、聚簇变成表服务。这样一来,数据湖表更接近一个有生命周期管理的系统,而不是一个不断堆文件的目录。
当然,Hudi 也不是免费的。它会带来更多参数、更多 bundle 兼容问题、更多表服务调优工作。特别是在 Spark、Flink、Hive、Hadoop 版本混在一起时,依赖冲突会很常见。所以这个系列后面不会只写概念,也会把真实安装、报错、截图和修复过程记录下来。
对我来说,是否引入 Hudi 可以用一句话判断:
如果这张数据湖表需要持续接收变更,并且下游希望稳定读取最新状态或增量变化,就应该认真考虑 Hudi;如果只是低频追加的离线归档表,普通 Parquet / Hive 表可能更直接。
4.2 总结
本文先从整体上解释了 Hudi 的定位。
- Hudi 不是简单的文件格式,而是数据湖表管理系统。
- 普通 Hive / Parquet 表适合追加场景,但在更新、删除、事务和增量消费上能力不足。
- Hudi 通过 Timeline、Index、FileGroup、FileSlice 和表服务,让数据湖具备更接近数据库的表管理能力。
- Spark、Flink、Hive、Trino 不是 Hudi 的替代品,而是从不同角度读写 Hudi 表的计算和查询引擎。
- CDC 入湖、近实时分析、增量 Pipeline、数据修复和增量导出,是 Hudi 比普通文件表更有价值的典型场景。