Hudi基础回顾:表类型、索引机制、文件布局与文件组概念
在深入理解 Clustering 聚簇优化之前,必须先建立对 Hudi 核心数据模型的清晰认知。Clustering 本质上是对文件布局的重组操作,如果对表类型、索引机制、文件组(FileGroup)等基础概念缺乏准确理解,后续的调优策略很容易出现偏差。本节将系统梳理这些基础概念,并重点说明它们与 Clustering 的关联。
2.1 表类型:Copy-on-Write 与 Merge-on-Read
Hudi 提供两种表类型,它们决定了数据写入时的文件处理方式,也直接影响 Clustering 的执行策略与收益。
| 对比维度 | Copy-on-Write (COW) | Merge-on-Read (MOR) |
|---|---|---|
| 写入方式 | 写入时同步合并,重写整个文件组 | 写入时追加到 Avro 日志文件,不合并 |
| 读取性能 | 读取快,无需合并 | 读取需合并日志与列文件,较慢 |
| 写入放大 | 高(每次更新重写文件) | 低(追加写) |
| Clustering 适用性 | 主要用于小文件合并与排序 | 除小文件合并外,还需将日志文件转化为列文件 |
COW 表每次更新都会生成新版本的文件组,旧版本文件在清理策略执行后被删除。MOR 表则通过日志文件(.log)记录增量更新,列文件(.parquet)保持只读。Clustering 在 MOR 表上的一个关键作用,就是将日志文件与列文件进行合并(即 Clustering 的 log file 合并模式),减少读取时的合并开销。
2.2 索引机制:定位文件组的关键
Hudi 的索引机制用于将记录(Record)映射到具体的文件组(FileGroup)。当一条更新记录到达时,Hudi 需要快速判断该记录属于哪个文件组,从而决定是追加还是合并。索引类型直接影响 Clustering 后索引的维护成本。
- Blob Index(默认):基于布隆过滤器,将记录键映射到文件组 ID。Clustering 后文件组 ID 不变,索引无需重建。
- Simple Index:通过查询全表来定位记录,Clustering 后需要重新比对,开销较大。
- HBase Index:外部索引存储,Clustering 不影响索引映射,但需要保证 HBase 表与 Hudi 表的一致性。
- Bucket Index:基于哈希分桶,Clustering 如果改变文件组数量,会导致桶映射失效,需谨慎配置。
在 Clustering 执行过程中,文件组内的文件会被重写,但文件组 ID(FileGroup ID)保持不变。这意味着索引中记录键到文件组 ID 的映射关系依然有效,无需全局重建索引。这是 Clustering 相比动态分区覆盖等操作的核心优势之一。
2.3 文件布局:文件组与文件片的层次结构
Hudi 表在存储层面遵循清晰的层次结构,理解这一结构是掌握 Clustering 的前提。下图展示了从表到文件片的完整层次关系:
上图展示了 Hudi 表存储的完整层次结构。核心概念如下:
- FileGroup(文件组):是 Hudi 中数据组织的基本单元,每个 FileGroup 包含同一主键的多版本数据。一个分区内可以有多个 FileGroup,每个 FileGroup 有唯一的 FileGroup ID。
- FileSlice(文件片):FileGroup 内按提交时间(commit time)划分的版本,每个 FileSlice 对应一次提交产生的数据版本。
- Base File(基础文件):列式存储文件(Parquet),保存已合并的完整数据。
- Log File(日志文件):行式存储文件(Avro),保存增量更新,仅在 MOR 表中存在。
2.4 文件组与 Clustering 的关联
Clustering 操作的核心目标是在不改变 FileGroup ID的前提下,对 FileGroup 内的文件进行重组。具体来说,Clustering 可以:
- 合并小文件:将多个小 FileSlice 合并为更大的文件,减少文件数量,提升查询效率。
- 排序数据:按照指定列(如时间戳、用户 ID)对文件内数据进行排序,提升谓词下推和跳跃读取的效率。
- 合并日志文件:在 MOR 表中,将多个日志文件与基础文件合并,生成新的基础文件,减少读取时的合并开销。
- 索引映射关系(记录键 → FileGroup ID)无需重建,节省了大规模索引维护成本。
- 正在进行的流式写入不会中断,因为新写入的数据会进入新的 FileSlice,与 Clustering 产生的 FileSlice 互不干扰。
- 查询引擎(如 Spark、Flink、Presto)无需感知 Clustering 的发生,它们仍然通过 FileGroup ID 定位数据。
2.5 常见误区与复盘
实际上,Clustering 只重组文件布局,不会改变记录的主键哈希分布。FileGroup ID 与主键的映射关系在写入时确定,Clustering 不会重新分配记录到不同的 FileGroup。
Compaction 是 MOR 表特有的操作,将日志文件合并到基础文件,解决读取时的合并开销。Clustering 则更广泛,不仅处理日志文件,还处理小文件合并、数据排序等。两者可以协同工作,但触发机制和执行策略不同。
虽然 FileGroup ID 不变,但 Clustering 会改变文件内的数据分布。如果使用 Bucket Index,且 Clustering 改变了文件组内的文件数量,可能导致桶内文件大小不均,影响查询性能。建议在配置 Clustering 策略时,评估索引类型的兼容性。
2.6 案例:理解文件组在 Clustering 中的角色
假设有一个订单表,分区 dt=2024-06-01 下有三个 FileGroup:
dt=2024-06-01/
├── FileGroup-001/
│ ├── FileSlice-001-0 (commit 20240601, 10MB)
│ └── FileSlice-001-1 (commit 20240605, 5MB)
├── FileGroup-002/
│ └── FileSlice-002-0 (commit 20240601, 8MB)
└── FileGroup-003/
└── FileSlice-003-0 (commit 20240603, 3MB)
执行 Clustering 后,可能的结果是:
dt=2024-06-01/
├── FileGroup-001/
│ └── FileSlice-001-2 (commit 20240610, 15MB) ← 合并了 001-0 和 001-1
├── FileGroup-002/
│ └── FileSlice-002-1 (commit 20240610, 8MB) ← 保持不变或重写
└── FileGroup-003/
└── FileSlice-003-1 (commit 20240610, 3MB) ← 保持不变或重写
注意:FileGroup ID(001、002、003)没有变化,但 FileSlice 的版本号更新了。Clustering 产生的新的 FileSlice 会替换旧的 FileSlice,旧文件在清理策略执行后被删除。整个过程对上层查询透明,索引无需重建。