文章目录
写操作
1)UPSERT:默认行为,数据先通过 index 打标(INSERT/UPDATE),有一些启发式算法决定消息的组织以优化文件的大小 => CDC 导入
2)INSERT:跳过 index,写入效率更高 => Log Deduplication
3)BULK_INSERT:写排序,对大数据量的 Hudi 表初始化友好,对文件大小的限制best effort(写 HFile)
写流程(UPSERT)
Copy On Write
1)先对 records 按照 record key 去重
2)首先对这批数据创建索引 (HoodieKey => HoodieRecordLocation);通过索引区分哪些 records 是 update,哪些 records 是 insert(key 第一次写入)
3)对于 update 消息,会直接找到对应 key 所在的最新 FileSlice 的 base 文件,并做 merge 后写新的 base file (新的 FileSlice)
4)对于 insert 消息,会扫描当前 partition 的所有 SmallFile(小于一定大小的 base file),然后 merge 写新的 FileSlice;如果没有 SmallFile,直接写新的 FileGroup + FileSlice
Merge On Read
1)先对 records 按照 record key 去重(可选)
2)首先对这批数据创建索引 (HoodieKey => HoodieRecordLocation);通过索引区分哪些 records 是 update,哪些 records 是 insert(key 第一次写入)
3)如果是 insert 消息,如果 log file 不可建索引(默认),会尝试 merge 分区内最小的 base file (不包含 log file 的 FileSlice),生成新的 FileSlice;如果没有 base file 就新写一个 FileGroup + FileSlice + base file;如果 log file 可建索引,尝试 append 小的 log file,如果没有就新写一个 FileGroup + FileSlice + base file
4)如果是 update 消息,写对应的 file group + file slice,直接 append 最新的 log file(如果碰巧是当前最小的小文件,会 merge base file,生成新的 file slice)
5)log file 大小达到阈值会 roll over(翻转) 一个新的
写流程(INSERT)
Copy On Write
1)先对 records 按照 record key 去重(可选)
2)不会创建 Index
3)如果有小的 base file 文件,merge base file,生成新的 FileSlice + base file,否则直接写新的 FileSlice + base file
Merge On Read
1)先对 records 按照 record key 去重(可选)
2)不会创建 Index
3)如果 log file 可索引,并且有小的 FileSlice,尝试追加或写最新的 log file;如果log file 不可索引,写一个新的 FileSlice + base file
写流程(INSERT OVERWRITE)
在同一分区中创建新的文件组集。现有的文件组被标记为 “删除”。根据新记录的数量创建新的文件组
COW
在插入分区之前 | 插入相同数量的记录覆盖 | 插入覆盖更多的记录 | 插入重写 1 条记录 |
---|---|---|---|
分区包含file1-t0.parquet,file2-t0.parquet。 | 分区将添加file3-t1.parquet,file4-t1.parquet。file1, file2 在 t1后的元数据中被标记为无效。 | 分区将添加file3-t1.parquet,file4-t1.parquet,file5-t1.parquet,…,fileN-t1.parquet。file1, file2 在 t1 后的元数据中被标记为无效 | 分区将添加 file3-t1.parquet。file1, file2 在 t1 后的元数据中被标记为无效。 |
MOR
在插入分区之前 | 插入相同数量的记录覆盖 | 插入覆盖更多的记录 | 插入重写 1 条记录 |
---|---|---|---|
分区包含file1-t0.parquet,file2-t0.parquet。.file1-t00.log | file3-t1.parquet,file4-t1.parquet。file1, file2 在 t1后的元数据中被标记为无效。 | file3-t1.parquet, file4-t1.parquet…fileN-t1.parquetfile1, file2 在 t1 后的元数据中被标记为无效 | 分区将添加 file3-t1.parquet。file1, file2 在 t1 后的元数据中被标记为无效。 |
优点
(1)COW 和 MOR 在执行方面非常相似。不干扰 MOR 的 compaction。
(2)减少 parquet 文件大小。
(3)不需要更新关键路径中的外部索引。索引实现可以检查文件组是否无效(类似于在 HBaseIndex 中检查 commit 是否无效的方式)。
(4)可以扩展清理策略,在一定的时间窗口后删除旧文件组。
缺点
(1)需要转发以前提交的元数据。
- 在 t1,比如 file1 被标记为无效,我们在 t1.commit 中存储 “invalidFiles=file1”(或者在 MOR 中存储 deltacommit)
- 在 t2,比如 file2 也被标记为无效。我们转发之前的文件,并在 t2.commit 中标记"invalidFiles=file1, file2"(或 MOR 的 deltacommit)
(2)忽略磁盘中存在的 parquet 文件也是 Hudi 的一个新行为, 可能容易出错,我们必须认识到新的行为,并更新文件系统的所有视图来忽略它们。这一点可能会在实现其他功能时造成问题。
Key 生成策略
用来生成 HoodieKey(record key + partition path),目前支持以下策略:
- 支持多个字段组合 record keys
- 支持多个字段组合的 parition path (可定制时间格式,Hive style path name)
- 非分区表
删除策略
1)逻辑删:将 value 字段全部标记为 null
2)物理删:
(1)通过 OPERATION_OPT_KEY 删除所有的输入记录
(2)配置 PAYLOAD_CLASS_OPT_KEY = org.apache.hudi.EmptyHoodieRecordPayload 删除所有的输入记录
(3)在输入记录添加字段:_hoodie_is_deleted
总结
通过对写流程的梳理可以了解到 Apache Hudi 相对于其他数据湖方案的核心优势:
1)写入过程充分优化了文件存储的小文件问题,Copy On Write 写会一直将一个bucket (FileGroup)的 base 文件写到设定的阈值大小才会划分新的 bucket;Merge On Read 写在同一个 bucket 中,log file 也是一直append 直到大小超过设定的阈值 roll over。
2)对 UPDATE 和 DELETE 的支持非常高效,一条 record 的整个生命周期操作都发生在同一个 bucket,不仅减少小文件数量,也提升了数据读取的效率(不必要的 join 和merge)。