2、Hudi基础回顾:表类型(COW/MOR)、文件组与文件片、索引与时间线
在深入 Compaction 积压诊断之前,必须先建立对 Hudi 核心数据模型的清晰认知。Compaction 的本质是 MOR 表特有的异步操作,而它的调度与执行又完全围绕文件组(File Group)与文件片(File Slice)展开。如果对这些基础概念理解不牢,后续排查积压原因时很容易将问题定位到错误的环节。本节将系统梳理 Hudi 的表类型、文件组织方式、索引机制与时间线(Timeline)语义,为后续章节的积压分析打下坚实基础。
2.1 表类型:COW 与 MOR 的本质差异
Hudi 提供两种表类型,它们决定了数据写入时是“原地更新”还是“增量追加”,也直接决定了 Compaction 是否存在。
- Copy on Write(COW):写放大严重,但读路径简单。每次更新都会重写整个 Parquet 文件,新提交直接生成新版本的文件片,旧文件片被标记删除。COW 表不存在 Compaction 概念,因为写入时已经完成了合并。
- Merge on Read(MOR):写路径先写 Avro 格式的增量日志(.log 文件),再通过 Compaction 将日志与基础 Parquet 文件合并成新文件片。MOR 表将合并成本延迟到后台异步执行,这正是 Compaction 积压问题的根源。
关键判断:如果你的集群中出现了 Compaction 积压,首先确认表类型是否为 MOR。COW 表永远不会产生 Compaction 任务,如果监控中看到相关指标,说明表属性配置有误或任务被错误触发。
两种表类型在写入路径上的差异可通过下表快速对比:
| 对比维度 | COW(写时复制) | MOR(读时合并) |
|---|---|---|
| 数据文件格式 | 仅 Parquet(列式) | Parquet(基础)+ Avro(增量日志) |
| 更新方式 | 重写整个文件组 | 追加写入 .log 文件 |
| Compaction 需求 | 无 | 必须(异步或同步) |
| 查询延迟 | 低(直接读列式文件) | 高(需合并日志与基础文件) |
| 写放大系数 | 高(频繁更新时) | 低(追加写为主) |
| 典型适用场景 | 低频更新、查询密集 | 高频写入、实时摄取 |
2.2 文件组与文件片:Compaction 的操作单元
理解文件组(File Group)与文件片(File Slice)的层级关系,是看懂 Compaction 调度日志的前提。Hudi 将一张表按主键哈希分为多个分区,每个分区内又包含多个文件组,每个文件组由一系列文件片组成。
- 文件组(File Group):以文件 ID(File ID)唯一标识,是 Hudi 数据组织的基本容器。同一文件组内的数据共享相同的主键哈希范围。
- 文件片(File Slice):文件组内的一次版本快照。每个文件片由“基础 Parquet 文件 + 若干增量 .log 文件”构成(COW 表则只有 Parquet)。新写入的更新记录会追加到当前最新文件片的 .log 文件中,直到 Compaction 将其合并生成新的文件片。
下图展示了 MOR 表文件组内的文件片演进过程,以及 Compaction 在其中扮演的“合并者”角色:
从图中可以看到,Compaction 的本质操作是:将当前最新文件片(含 .log)合并为一个新的基础文件片,并清理旧文件片。积压发生时,通常表现为某个文件组内存在多个未合并的 .log 文件,或者单个 .log 文件体积异常庞大。
2.3 索引机制:定位文件组的“导航系统”
Hudi 的索引(Index)用于将一条记录的记录键(Record Key)映射到具体的文件组 ID。索引机制直接影响写入效率,也与 Compaction 积压存在间接关联——如果索引查询缓慢,写入任务会长时间占用资源,间接拖慢 Compaction 调度。
Hudi 支持多种索引实现,生产环境中常见的有以下三种:
- Bloom Filter Index(布隆过滤器索引):默认索引。在每个文件片的基础 Parquet 文件中维护布隆过滤器,写入时通过布隆过滤器快速判断记录可能存在的文件组。优点是无需额外存储,缺点是存在误判率,且文件组数量多时过滤效率下降。
- HBase Index:将记录键到文件组 ID 的映射存储在外部 HBase 表中。查询精确、速度快,但引入外部依赖,运维成本高。
- Simple Index:全表扫描所有文件组进行匹配,仅适合小表或测试环境,生产环境几乎不用。
与 Compaction 的关联:当使用 Bloom Filter Index 时,如果 Compaction 积压导致文件组内文件片过多,布隆过滤器的检查范围会扩大(需要检查更多文件片),从而拖慢写入时的索引查询。这形成了一个反馈回路:Compaction 积压 → 索引变慢 → 写入变慢 → 资源更紧张 → Compaction 更慢。
2.4 时间线(Timeline):Hudi 的“操作日志”
Hudi 的时间线(Timeline)是表上所有操作的元数据日志,记录了从表创建以来的每一次提交(Commit)、压缩(Compaction)、清理(Clean)等操作。时间线是诊断 Compaction 积压的核心工具——通过查看时间线上的 Instant 状态,可以精确判断积压发生在哪个环节。
时间线上的每个操作称为一个 Instant(瞬时操作),包含三个关键属性:
- Instant 时间戳:操作的全局唯一时间标识,格式通常为
yyyyMMddHHmmss。 - 操作类型:COMMIT(提交)、COMPACTION(压缩)、CLEAN(清理)、ROLLBACK(回滚)等。
- 状态:REQUESTED(已请求)、INFLIGHT(执行中)、COMPLETED(已完成)。
对于 Compaction 积压诊断,重点关注时间线上 COMPACTION 类型的 Instant 状态:
# 通过 Hudi CLI 查看时间线示例
compactions scheduled for: 20240601120000
compaction instants:
20240601120000 COMPACTION REQUESTED
20240601121500 COMPACTION INFLIGHT
20240601123000 COMPACTION REQUESTED <-- 积压:多个 REQUESTED 未执行
20240601124500 COMPACTION REQUESTED
20240601130000 COMPACTION INFLIGHT <-- 当前正在执行
当时间线上出现大量 REQUESTED 状态的 Compaction Instant 时,说明调度器已经生成了压缩计划,但执行器未能及时处理。这通常意味着:
- 执行 Compaction 的资源(Spark 或 Flink 任务槽)不足。
- Compaction 计划生成过于频繁(例如每次写入都触发调度)。
- 单个 Compaction 任务处理的数据量过大(文件组内 .log 文件过多)。
2.5 核心误区与复盘
在理解 Hudi 基础概念时,以下几个误区经常导致后续 Compaction 问题排查方向错误:
误区一:认为 COW 表也需要 Compaction
COW 表在写入时已经完成了合并,不存在独立的 Compaction 操作。如果监控面板上看到 COW 表出现 Compaction 指标,通常是表属性被错误修改(例如将 hoodie.table.type 从 COPY_ON_WRITE 改成了 MERGE_ON_READ),或者任务配置中强制指定了 Compaction 调度。
误区二:混淆“文件片数量”与“文件组数量”
Compaction 积压的直接表现是文件片数量过多(尤其是包含 .log 的文件片),而不是文件组数量多。文件组数量由主键分布决定,通常保持稳定;而文件片数量会随每次写入增长。诊断时应该关注每个文件组内有多少个未合并的文件片,而不是统计总文件组数。
误区三:忽略时间线上的 ROLLBACK 操作
如果写入任务失败并触发 ROLLBACK,时间线上会留下回滚记录。回滚操作会清理未完成的文件片,但也可能阻塞后续 Compaction 调度。在查看时间线时,如果发现大量 ROLLBACK 与 COMPACTION 交错出现,需要优先排查写入失败的原因,而不是直接调大 Compaction 并发。
复盘一个典型场景:某团队发现 MOR 表查询延迟从 200ms 飙升到 2s,首先检查了文件组数量,发现数量正常(约 500 个),于是怀疑是集群资源问题。但进一步查看时间线后发现,某个文件组内积累了 7 个文件片,其中 5 个包含 .log 文件,最大的 .log 文件已达 1.2GB。查询时需要合并这 5 个日志文件,导致延迟剧增。根本原因是 Compaction 调度间隔设置过长(hoodie.compact.inline.max.delta.commits 设为 10),而该表写入频率极高,每 5 分钟就有一次提交,导致积压迅速累积。调整参数为 3 后,Compaction 及时跟进,查询延迟恢复正常。