【Hudi】 更新怎么定位 — File Group + Index

17 阅读 1290 字 · 约 5 分钟

一条更新来了,Hudi 怎么知道要改哪个文件?这就是 File Group 和 Index 要解决的事。

为什么不能扫全表

最简单的更新方案是:找出所有分区里所有 Parquet 文件,逐个读出来,找到要改的行,写回去。这就是全量重写的逻辑。

  • 一张 100GB 的表,改一行也要扫 100GB
  • 每次更新都是全量扫描,延迟随数据量线性增长
  • 多条更新同时来,只能串行——后写的等前写的重写完

写放大不只是磁盘 IO 的问题,更是 CPU、网络、时间的浪费。要把更新做成"定点修改",核心思路是:知道要改的行在哪个文件里

File Group: 把数据分桶

Hudi 的第一层优化是分桶。写入时,Hudi 把每条记录按 recordKey hash 到一个 file group:

recordKey = "order_12345"
hash(recordKey) % numGroups = 3  →  这条记录进 file_group_3

同一个 file group 里的数据是独立的——更新 order_12345 只动 file_group_3,其他 file group 完全不碰。

这和分库分表是一个思路:把大的切小,把全量扫变局部扫。file group 的数量越多,每个 group 越小,单次更新的范围越窄。但也不能太多——文件太碎反而影响读性能。

Index: key → file 的快速查找表

分桶解决了"缩小范围",但没解决"快速定位"。Hudi 还要知道 order_12345 具体在哪个 file group 的哪个文件里。

这就是 Index 的事。Index 本质上是个映射表:

recordKey → (fileGroupId, fileId)

Hudi 提供几种 index 实现,核心区别在于这个映射存在哪里

Bloom Index

在每个 data file 的 footer 里嵌一个 Bloom filter。Bloom filter 是经典的概率结构:能确定"某个 key 一定不在这个文件里",但"可能在"有概率误判。

流程是:

  1. 对每个 data file 查 Bloom filter
  2. 筛掉"一定不在"的文件
  3. 对剩下"可能在"的文件,逐行比对找到真正的记录

大多数 key 被 Bloom 筛掉,实际只读很少的文件。

Bucket Index

更直接:写入时按 hash 分桶,同一个 bucket 的记录永远在同一个 file group。更新时算一下 hash 就知道在哪。

不需要 Bloom filter,不需要逐行比对,O(1) 定位。但要求写入时就定好分桶规则,不能动态调整。

HBase Index

把映射存在外部 HBase 里。适合超大规模——几十亿 key,内存放不下的场景。

代价是多了个外部依赖。HBase 挂了索引就废,HBase 慢了更新就慢。


三种 index 各有取舍。实际选型时 Bloom Index 是默认,大多数场景够用。Bucket Index 适合确定性强的场景(分区键和主键能对齐)。HBase Index 是最后的手段,非必要不引入。

小结

Hudi 的更新不扫全表,靠两层:

  1. File Group 把数据按 hash 分桶,把全量扫变局部扫
  2. Index 在局部范围内做 key→file 的快速定位

两个加起来,改一行从"重写整个表"变成了"读一两个文件"。这就是 Hudi 能做行级更新的核心。