一、引言
在Hudi实时数据摄入(Streaming Ingestion)场景下,为了保证数据的低延迟可见性,写入通常以较高频率、较小批次进行。这不可避免地产生大量小文件,带来以下问题:
- 查询性能下降:查询引擎需要打开大量文件句柄,元数据开销急剧增加
- 存储效率降低:小文件的 Parquet/ORC Footer 等元数据占比过高
- 资源浪费:NameNode(HDFS)或对象存储的 LIST 操作开销增大
除了文件大小问题,数据在物理存储上的排列顺序直接影响查询的 Data Skipping 效率。如果高频查询字段的数据散落在所有文件中,则无法有效利用 Min/Max 统计信息做文件级别裁剪。
Clustering 本质上是 Hudi Table Service 的一种后台数据重组织操作,其核心目标是:
- 合并小文件:将多个小文件合并为大小合理的文件
- 重排数据布局:按指定列对数据进行排序/聚簇,提升 Data Skipping 效率
- 不阻塞写入:作为异步 Table Service,与 Ingestion 并行执行
二、Clustering 核心原理
Clustering 遵循 Hudi Table Service 的标准两阶段提交模型:Schedule(调度)→ Execute(执行)→ Commit(提交)。

1.调度阶段(Schedule)
调度阶段负责确定哪些 File Group 需要参与 Clustering,其核心逻辑包括:
分区选择策略(Partition Selection Strategy):
RecentDaysPartitionSelectionStrategy:选择最近 N 天的分区SelectedPartitionsPartitionSelectionStrategy:指定特定分区AllPartitionsSelectionStrategy:扫描所有分区
File Group 选择策略(File Group Selection Strategy):
- 基于文件大小:小于目标大小的文件优先选入
- 基于文件数量:限制单次 Clustering 涉及的最大文件组数
调度完成后,生成 ClusteringPlan 写入 Timeline,状态为 .requested。
2.执行阶段(Execute)
执行阶段的核心是数据的物理重组织,涉及两个关键策略:
排序策略(Sort Strategy):
| 策略 | 说明 | 适用场景 |
|---|---|---|
| Linear Sort | 按单列排序 | 单一查询维度 |
| Z-Order Sort | 多列空间填充曲线排序 | 多维查询过滤 |
| Hilbert Sort | Hilbert 曲线排序 | 多维场景,局部性优于 Z-Order |
执行引擎:Clustering 的执行可由 Spark、Flink 等引擎驱动,本质上是一个分布式的 "读取 → 排序 → 写入" 过程。
3.提交阶段(Commit)
执行完成后,提交阶段将:
- 将新生成的 Base File 注册到元数据中
- 将旧的 File Slice 标记为 Replaced
- 在 Timeline 上将该 Instant 状态从
.inflight变为.completed
三、执行模式详解
1.Inline Clustering
随写入同步执行,适用于批处理或对延迟不敏感的场景:
sql
Writer Commit N
│
▼
触发条件满足? ──── 否 ──▶ 结束
│
是
▼
Schedule Clustering Plan
│
▼
Execute Clustering (同一 Job)
│
▼
Commit Clustering
│
▼
Writer Commit N+1
2.Async Clustering
独立于写入流程异步执行,是生产环境推荐模式:
sql
┌─────────────────────────────────────────────────────┐
│ Ingestion Pipeline (持续写入) │
│ Commit 1 → Commit 2 → Commit 3 → Commit 4 → ... │
└─────────────────────────────────────────────────────┘
│
┌───────────────────────────┘
▼
┌─────────────────────────────────────────────────────┐
│ Clustering Service (独立调度) │
│ Schedule → Execute → Commit │
│ (可由独立 Spark/Flink Job 驱动) │
└─────────────────────────────────────────────────────┘
3. 1.x 中的 Table Service 统一管理
Hudi 1.x 提供了更统一的 Table Service 管理方式:
- HoodieTableServiceClient:统一调度 Compaction、Clustering、Cleaning
- 支持独立部署:Table Service 可作为独立服务运行,与 Writer 解耦
四、最佳实践
1.排序列选择
scss
┌─────────────────────────────────────────────────┐
│ 排序列选择决策树 │
└─────────────────────────────────────────────────┘
查询模式分析
│
├── 单一高频过滤列 ──▶ Linear Sort
│ (如: date, region) 按该列排序即可
│
├── 2~4 个常用过滤列 ──▶ Z-Order / Hilbert
│ (如: city + category 多维聚簇
│ + event_type)
│
└── 无明确过滤模式 ──▶ 仅做文件合并
不指定排序列
建议:
- 选择 WHERE 子句中出现频率最高的列
- 优先选择基数(Cardinality)适中的列(过高或过低都不理想)
- Z-Order 列数建议不超过 4 列,否则效果递减
2.目标文件大小调优
- HDFS 场景:建议 128MB ~ 256MB(与 HDFS Block Size 对齐)
- 对象存储(S3/OSS/GCS):建议 256MB ~ 512MB(减少 LIST 开销)
- 注意:文件过大会导致单个 Task 处理时间过长,影响 Clustering 效率
3.调度频率
sql
推荐策略:
┌─────────────────────────────────────────────────────────┐
│ 摄入频率 │ Clustering 调度建议 │
├─────────────────────────────────────────────────────────┤
│ 分钟级实时摄入 │ 每 4~8 次 Commit 触发一次 Schedule │
│ 小时级批摄入 │ 每 1~2 次 Commit 触发一次 │
│ 天级批摄入 │ 每次 Commit 后触发 │
└─────────────────────────────────────────────────────────┘
关键配置:
ini
# 每 N 次 commit 触发一次 clustering schedule
hoodie.clustering.inline.max.commits=4
# 异步模式下的配置
hoodie.clustering.async.max.commits=4
4.资源配置建议
ini
# 控制 Clustering 并行度
hoodie.clustering.plan.strategy.max.num.groups=30
# 单个 Group 内最大文件数
hoodie.clustering.plan.strategy.max.bytes.per.group=2147483648
# Spark 执行时的 shuffle 分区数(根据数据量调整)
hoodie.clustering.plan.strategy.daybased.lookback.partitions=2
5.生产环境注意事项
- 避免 Clustering 与 Compaction 同时大规模执行:两者都是 IO 密集操作,建议错开调度
- 监控 Clustering 耗时:如果单次 Clustering 耗时超过写入间隔,考虑减小
max.num.groups - Clustering 期间的查询一致性:Hudi 保证 Snapshot Isolation,正在 Clustering 的数据仍可正常读取
- 失败重试:Clustering 是幂等操作,
.inflight状态的 Plan 在 Writer 重启后会自动回滚或重试