前言
Hudi 系列文章在这个这里查看 github.com/leosanqing/…
索引(Index)是 Hudi 最重要的特性之一,也是区别于之前传统数仓 Hive 的重要特点, 是实现 Time Travel, Update/Delete,事务 等重要特性的基础
Hudi provides efficient upserts, by mapping a given hoodie key (record key + partition path) consistently to a file id, via an indexing mechanism
Hudi 通过索引机制提供了高效的 upsert, 索引机制是通过映射 HoodieKey( recordKey+partition) 与 File Id 实现. 如果是非全局索引就不包括 partition
问题
作用
减少开销怎么理解
从上面官网的图可以看出来,没有索引和有索引的开销
如果没有索引, 因为我并不知道我要更新的数据在哪些文件中, 我每次的要实现更新需要访问所有的基础文件, 需要这么多 IO 的开销**(100+25) * 8 = 1200MB**
如果有索引, 我知道这些数据应该更新到哪些基础文件, 我只要找特定的文件就行, 所以开销为 (100+25*2) * 4 = 600MB
hive因为没有索引,所以他不支持变更操作(update/Delete),一次写入不能变更. 因为哪怕变更一条数据, 我都需要访问hdfs 上所有的文件(如果没有分区,有分区的话访问特定的分区下的所有文件),挨个比较主键, 然后重写之后上传到 hdfs
数据变更基础
因为我变更的时候知道了我这个数据应该去哪个文件找, 重写的成本就能接受了. 并且 Upsert 的时候我也能根据索引判断, 我这条写进来的数据应该是 Insert 还是应该 Update
以 COW 表,upsert 写入为例(当然上述步骤会根据索引类型和计算引擎有不同的实现和步骤,但是大体为上面的步骤)
数据进来 --> 计算主键 --> 根据主键查询索引判断是 Insert 还是 Update --> 根据 Insert 还是 Update 标记写入文件 -- >写入时, 写到标记的文件,更新的更新,插入的插入--> 更新索引
类型
重要的索引类型具体会放在源码分析中详细分析,这里只简单讲个概念
- BLOOM: 采用根据RecordKey构建的布隆过滤器,还可以选择使用RecordKey范围修剪候选文件。在分区内强制执行键唯一性。
- GLOBAL_BLOOM: 采用根据RecordKey构建的布隆过滤器,还可以选择使用RecordKey范围修剪候选文件。表中的所有分区都强制执行键唯一性。
- SIMPLE(Spark 引擎的默认值): Spark 引擎的默认索引类型。根据从存储上的表中提取的键对传入记录执行lean join。分区内强制执行键唯一性。
- GLOBAL_SIMPLE: 根据从存储上的表中提取的键对传入记录执行lean join。表中的所有分区都强制执行键唯一性。
- HBASE: 管理外部 Apache HBase 表中的索引映射,是全局索引。
- INMEMORY(Flink 和 Java 的默认值): 使用 Spark 和 Java 引擎中的内存中 hashmap 以及 Flink 中的 Flink 内存中状态进行索引。
- BUCKET:使用桶哈希来定位包含记录的文件组。尤其是在大规模的情况下是有利的。使用
hoodie.index.bucket.engine
选择bucket引擎类型,即如何生成bucket;SIMPLE(默认)
:为每个分区的文件组使用固定数量的存储桶,无法缩小或扩展。这适用于 COW 和 MOR 表。由于存储桶的数量无法更改,并且存储桶和文件组之间采用一对一映射的设计,因此该索引可能不太适合高度倾斜的分区。CONSISTENT_HASHING
:支持动态数量的存储桶,并调整存储桶的大小以正确调整每个存储桶的大小。这解决了潜在的数据倾斜问题,即可以动态调整具有大量数据的分区的大小以具有合理大小的多个存储桶,这与 SIMPLE 存储桶引擎类型中每个分区的固定数量的存储桶不同。这仅适用于 MOR 表。
- RECORD_INDEX: 将RecordKey保存到 HUDI 元数据表中的位置映射的索引。记录索引是全局索引,强制表中所有分区的键唯一性。支持分片以实现非常大的规模。
- 自定义索引: 你可以扩展这个[publicAPI](github.com/apache/hudi… /org/apache/hudi/index/HoodieIndex.java) 来实现自定义索引。
全局索引
从上面类型看,有个GLOBAL 开头的就是全局索引,还包括 HBase 索引
全局索引的意思是, 一个recordKey, 不管你在不在同一个分区,有且只能有一个;非全局是只要我分区不相同,那我就是可以同时存在多个相同的 recordKey
比如 我是一个分区表, 我有两条数据 {id:1, county:China}, {id:1, country: Janpan},顺序写入. 分区为 country, recordKey 是id.
如果是全局索引,那我最后只会有一条数据 {id:1, country: Janpan}
如果是非全局索引,这两个数据都hui存在
Flink
Flink 只有三种索引: InMemory(FlinkState) 和 Bucket(SIMPLE, CONSISTENT_HASHING)
有时候我们看代码会感到疑惑,为啥源码里面,flink 列出了这么多索引,你却说只有三种,具体可以看这个 PR 中的 Comment github.com/apache/hudi…
虽然有个 类上面写了这么多,根本没有用, 最终只有 pipeline 初始化才有用.这里只有两种方式(官方说这个以后会报错,如果填写其他类型,flink 会报错)
真正的逻辑在这里 pipelines 类,所以看 flink web UI 的时候才会出现这样的情况:
桶索引没有 bucketAssigner,有 bucketWrite 算子
如果是 Flink State index 的任务 是 stream_write, 和 bucketAssigner算子
Spark
spark 除了 InMemory 其他都支持
总结