目录
- 基本概念
- 时间轴(TimeLine)
- Instant action:在表上执行的操作类型
- Instant time
- State
- 两个时间概念
- 文件布局(File Layout)
- 索引(Index)
- 原理
- 索引选项
- 全局索引与非全局索引
- 索引的选择策略
- 表类型(Table Types)
- 查询类型
- Snapshot Queries
- Incremental Queries
- Read Optimized Queries
- 不同表支持的查询类型
基本概念
时间轴(TimeLine)
Hudi的核心是维护表上在不同的即时时间(instants)执行的所有操作的时间轴(timeline),这有助于提供表的即时视图,同时还有效地支持按到达顺序检索数据。一个instant由一下三个部分组成:
Instant action:在表上执行的操作类型
- commits:一次xommit表示将一批数据原子性地写入一个表
- cleans:清除表中不在需要的旧版本文件的后台活动
- delta_commit:增量提交指的是将一批数据原子性地写入一个MergeOnRead类型的表,其中部分或所有数据可以写入增量日志
- compaction:合并Hudi内部差异数据结构的后台活动,例如:将更新操作从基于行的log日志文件合并到列式存储的数据文件。在内部,compaction体现为timeline上特殊提交。
- rollback:表示当commit/delta_commit不成功时进行回滚,其会删除在写入过程中产生的部分文件。
- savepoint:将某些文件组标记为已保存,以便其不会被删除。在发生灾难需要恢复数据的情况下,他有助于将数据集还原到时间轴上的某个点。
Instant time
通常是一个时间戳(例如20190117010349),他按照动作开始时间的顺序单调增加
State
- requested:表示某个action已经调度,但尚未执行。
- inflight:表示action当前正在执行
- completed:表示timeline上的action已经完成
两个时间概念
区分两个重要的事件概念
- Arrival time:数据到达Hudi的时间,commit time.
- Event time:record中记录时间
文件布局(File Layout)
Hudi将一个表映射为如下文件结构
Hudi存储分为两个部分:
(1)元数据:.hoodie目录对应着表元数据信息,包括表的版本管理(Timeline)、归档目录(存放过时的instant也就是版本),一个instant记录一次(commit)的行为、时间戳和状态,Hudi以时间轴的形式维护了在数据集上执行的所有操作的元数据;
(2)数据:和Hive一样,以分区方式存储数据;分区里面存放着Base File(.Parquet) 和Log File(.log.*);
-
Hudi将数据表组织成分布是文件系统基本路径(basepath)下的目录结构
-
表被划分为多个分区,这些分区是包含该分区的数据文件的文件夹,非常类似Hive表
-
在每个分区中,文件被组织成文件组,由文件ID唯一标识
-
每个文件组包含几个文件片(FileSlice)
-
每个文件包含:
1)一个基本文件(.parquet):在某个commit/compaction即时时间(instant time)生成的(MOR)可能没有
2)多个日志文件(.log.*),这些日志文件包含生成基本文件以来对基本文件插入更新(COW没有) -
Hudi采用了对版本并发控制
1)compaction操作:合并日志和基本文件以产生新的文件片
2)clean操作:清除不使用/旧的文件片以回收文件系统上的空间
-
Hudi的base file(parquet文件)在footer的meta去记录了record key 组成的BloomFilter,用于在file base index的实现中实现高效的key contains检测,只有不存BloomFilter的key才需要扫描整个文件消灭假阳
-
Hudi的log是自己编码的,通过积攒数据buffer以LogBlock为单位写出,每个LogBlock包含magic number、size、content、footer等信息,用于数据读、校验、过滤。
索引(Index)
原理
Hudi是通过索引机制提供高效的upserts,具体是指将给定的hoodie key(record key + partition path)与文件id(文件组)建立唯一映射。这种映射关系,数据第一次写入文件后保持不变,索引,一个FileGroup包含了一批record的所有版本记录。Index用于区分消息是INSERT 还是 UPDATE.
索引选项
名称 | 备注 |
---|---|
Bloom索引 | 采用根据记录key构建的布隆过滤器,还可以选择使用记录key范围修剪候选文件。 |
GLOBAL_BLOOM索引 | 与Boolm索引类似,但是作用范围是全局 |
Simple索引 | 针对从存储上的表中提取的键对传入的更新/删除记录执行精益联接。 |
GLOBAL_SIMPLE索引 | 与Simple类似,但是作用范围是全局 |
HBase索引 | 将index信息保存到Hbase当中。 |
INMEMORY索引 | 在Spark、Java程序、Flink的内存中保存索引信息,Flink和Java默认使用当前索引 |
BUCKET索引 | 使用桶hash的方式定位文件组,在大数据量情况下效果较好。可以通过hoodie.index.bucket.engine指定bucket引擎。 |
RECORD_INDEX索引 | 索引将record的key保存到 Hudi元数据表中的位置映射。 |
自定义索引 | 自定义实现的索引。 |
全局索引与非全局索引
-
全局索引:全局索引在全表的所有分区范围下强制要求键的唯一性.全局索引提供了更强的保证,但是随着表增大,update/delete操作损失的性能越高。因此,更适合于小表。
-
非全局索引:默认的索引实现,只能保证数据在分区的唯一性。非全局索引一开写入器为同一个记录的update/delete提供一致性的分区路径,同时大幅度提高了效率,更适用于大表。
从index的维护成本和写入性能的角度考虑,维护一个global index的难度更大 对写入性的影响也更大,所以需要non-global index.
索引的选择策略
- 对事实表的延迟更新
大部分更新会发生在最新的几个分区上而小部分会在旧的分区;采用布隆索引
Hudi缓存了输入记录并使用了自定义分区器和统计规律来解决数据倾斜。如果布隆过滤器的伪正率过高,查询会增加数据的打乱操作。Hudi支持动态布隆过滤器(设置hoodie.bloom.index.filter.type = DYNAMIC_V0
),它可以根据文件里存放的记录数量来调整大小而达到设定的假阳性率。 - 对事件表去重
抵消耗去重是一个非常有挑战的工作。虽然可以用一个键值存储来实现去重(即Hbase索引),但索引存储的消耗会随着事件数增长而线性增长以至于变得不可行。事实上,有范围剪裁功能的布尔索引是最佳的解决方案
- 对维度表的随机更删:
使用简单索引更适合
表类型(Table Types)
-
Copy On Write(COW)
在COW表中,**只有数据文件/基本文件(.parquet),没有增量日志文件(.log.*).**对每一个新批次写入都将创建相应数据文件的新版本(新的FileSlice),新版本文件包包括旧版本文件的记录以及来自传入批次的记录(全量最新)。
由于再写入期间进行合并
,COW会产生一些写入延迟。但是COW的优势在于他的简单性,不需要其他表服务(如压缩),也相对容易调试。 -
Merge On Read(MOR)
MOR表中,包含列存的进本文件(.parquet)和行存的增量日志文件(基于行的avro格式,.log.*)MOR表的合并成本在读取端
在写入期间不会合并或创建较新的数据文件版本。标记/索引完成后,对于具有要更新记录的现有的数据文件。
读取端将实时合并基本文件及各自的增量文件日志。每次的读取延迟比较高
。所以Hudi使用压缩机制来将数据文件和日志合并在一起并创建更新版本的数据文件。 -
COW/MOR的对比
查询类型
Snapshot Queries
快照查询,可以查询指定commit/delta commit即时操作后表的最新快照
在读取合并(MOR)表的情况下,它通过即时合并最先新文件片的基本文件和增量文件来提供近实时表(几分钟)。
对于写时复制(COW),它可以替代现有的parquet表(或相同基本文件类型的表),同时提供upsert/delete和其他写入方面的功能,可以理解为查询最新版本的parquet数据文件;
下图是COW的快照查询:
Incremental Queries
增量查询,可以查询给定Commit/delta commit即时操作来查询新写入的数据。有效的提供变更流来启动增量数据管道。
Read Optimized Queries
读优化查询,可查看给定的commit/compact即时操作的表的最新快照。仅将最新文件片的基本/列文件暴露查询,并保证非Hudi表相同的列查询性能。
下图是MOR表的快照查询与读优化查询的对比:
Read Optimized Queries是对Merge On Read 表
类型快照查询的优化;
不同表支持的查询类型