1. 索引机制
hudi的索引机制是为了加速upsert/delete操作,它维护着(分区 + key)-> fileID之间的映射关系,所以可以减少对非必要base文件的合并
key是指索引key,可以是表的任意字段,在全局索引中常用主键key作为索引
1.1 索引类型
当前hudi支持以下类型的索引:
- INMEMORY:基于内存hashmap,为全局索引
- HBASE:基于外部存储hbase做索引,天然是全局索引
- SIMPLE, GLOBAL_SIMPLE:简单索引,将更新的key与base文件提取的key值进行join,分为全局和非全局
- BLOOM, GLOBAL_BLOOM:基于布隆过滤器,存在假阳性问题,分为全局和非全局
- BUCKET:由字节跳动为了弥补bloom不足而贡献的分桶索引,非全局
- FLINK_STATE:基于flink状态的全局索引
1.2 全局与非全局索引
- 全局索引:必需存在表级别的唯一的索引key,每个索引key在所有分区中都只能唯一对应一条记录(一般可以采用hoodie record key,即主键)。update/delete时查找索引的复杂度是O(表记录数),所以适合小表场景。
- 非全局索引:索引key在分区内唯一,对索引的查询只涉及本次update/delete记录的分区,所以它复杂度为O(更删记录数),适合数据量大的分区表。
2. 基于flink写的索引
虽然hudi的索引类型有这么多种,但是在使用flink往hudi写时只支持两种索引方式:BUCKET和FLINK_STATE
2.1 优化重构
在hudi的写过程中默认会调HoodieIndex#tagLocation
来查找索引批量地给记录确定位置,逻辑位于BaseWriteHelper#write
中,并且将查找索引花费的时间作为本次写的统计指标保存在HoodieWriteMetadata#indexLookupDuration
中。
在流式引擎中,每一次都以批量的方式计算记录的位置会给系统造成很大压力,应该流式地去计算或者获取
基于flink的写过程FlinkWriteHelper
重载去掉了查找索引的过程:
- 基于BUCKET的索引方式根本就不存在一份所谓的“索引数据”,它在写到文件中前根据哈希公式进行映射
- 基于FLINK状态的索引方式在
BucketAssignFunction
中流式地对每条记录进行了处理
2.1 索引对比
- BUCKET:预先指定固定的分桶数(
hoodie.bucket.index.num.buckets
),所以记录可以按分桶字段(hoodie.bucket.index.hash.field
)进行哈希计算得到所在分区的桶序号,每个桶代表一个数据文件。该索引方式的特点为,每个分区下的数据文件数固定,这样才保证可以固定(分区,索引key)->fileID之间的映射,不用花费额外的空间存储索引数据,我们需要预估每个分区的数据量以确定分桶数。 - FLINK_STATE:以表的主键作为索引key,每个key的索引数据是一个
ValueState
,所以它天然是全局索引。是默认的索引方式,由于使用了flink的状态,所以使用时需要考虑状态过大给Job带来的问题,有一点需要注意的是,默认情况下状态并没有设置失效日期index.state.ttl