作者:吴文池
背景
hudi在数据聚集方面,支持使用zorder对数据进行重排。
做zorder排序主要流程分为三步:
-对于用户指定的每个zorder字段,生成对应的z值。
-把所有zorder字段生成的z值进行比特位的交叉组值,生成最终的z值。
-根据最终z值,对所有数据进行排序。
在上面第一步中,每个字段生成自己的z值方式主要有两种:
-直接的值映射方式。该方式实现简单,容易理解,但也有缺陷:
参与生成z值的字段理论上需要是从0开始的正整数,这样才能生成很好的z曲线,但真实的数据集中基本不可能出现,那么zorder的效果将会打折扣。
对于一些前缀相同的数据,例如url字段大多数都以:http://www.开头,那么取前面几位做排序将毫无意义。
-先对数据采样,根据采样数据对所有数据进行分区,最后使用该数据所对应的分区号作为该数据的z值。这种方式可以很好的解决方式一的两个问题:
分区一定是从0开始的整数。
对于前缀相同的数据,也能够根据字符串大小,将其很好的分配到不同分区中,得到不同的z值。
下面主要介绍第二种方式采样分区流程。
(代码以hudi的master分支、commit 22c45a7704cf4d5ec6fb56ee7cc1bf17d826315d 为准)
采样分区流程
整体流程图
(采样分区总体流程图,里面一些详细的计算流程分析可参考下面代码分析)
代码分析
- 获取采样结果
// 该函数主要对rdd进行采样,并返回采样结果(采样结果包含采样的数据和该数据对应的权重)
def getRangeBounds(): ArrayBuffer[(K, Float)] = {
// zEncodeNum:即目的分区个数,用户可配置,配置项为:hoodie.layout.optimize.build.curve.sample.size
// 默认分区个数为200000
if (zEncodeNum <= 1) {
ArrayBuffer.empty[(K, Float)]
} else {
// samplePointsPerPartitionHint:每个分区采样个数,默认为20
// sampleSize:需要采样的个数,最多为1e6个
val sampleSize = math.min(samplePointsPerPartitionHint.toDouble * zEncodeNum, 1e6)
// rdd.partitions:rdd的分区个数
// 这里假设每个分区的数据都比较平衡,且每个分区的数据量都比较多,
// 所以多采集一些(因为实际上会出现分区不