Apache Kylin 性能优化
聚合组 Aggregation Groups
Cube Designer 的 Advanced Setting 中可以配置 Aggregation Groups。 理论上 N 维度 Cube 会构建 2^N 个 Cuboid,随着维度的增多,Cuboid 数量会指数增长,存储空间占用增大,构建时间增长。聚合组的目的是对维度分组,减少不必要的维度组合,从而减少 Cuboid 的数量。
分组之后的维度集合支持必要维度,层级维度,联合维度 3 种配置。
必要维度 Mandatory Dimension
用户对一个或几个维度特别敏感,所有的查询中都存在 GROUP BY 这些维度,则可以把这些维度配置为必要维度。
层级维度 Hierarchy Dimensions
具有层级关系的维度,例如国家 country,省份 province,城市 city。这三个维度的查询有以下三类:
- group by country
- where country='xxx' group by province
- where country='xxx' and province='zzz' group by city
联合维度 Joint Dimensions
联合维度表示不可分割的维度集合。
Rowkeys 优化
Kylin 使用 HBase 作为 Cube 的存储引擎。HBase 是 Hadoop 生态的 Key-Value 数据库,支持按 Key 随机查询和写入,这个 Key 在 HBase 中叫做 Rowkey。
维度编码 Encoding
对维度编码,可以将维度值转化为长度一致的字节,合适的编码方式能够有效减少对空间的占用,加速查询效率。
-
dict 字典编码,将提取纬度的唯一值在内存中构造字典,Rowkey 使用字典的 key 替换纬度值。 字典常驻内存,对查询影响很小,但是字典不能超过缓存大小,否则在 Cube build 过程中会发生缓存溢出。默认 dict 编码字段基数在
5,000,000
以内,可以通过kylin.dictionary.max.cardinality
修改,这种情况更好的做法是使用其他编码类型。 dict 字典适用中低基维度,如地区信息,国家,省份,城市。 -
fixed_length 使用固定长度 N 存储维度值,超过长度部分会截断。如果 N 很大,会造成 Rowkey 过长,HBase 性能下降。 fixed_length 适用超高基场景,如电话号码,IP 等。
-
fixed_length_hex 针对值为十六进制的维度,比如 Base64 之后的值。
-
integer 整数类型,不做编码转换。
Length
长度范围为 1~8,支持的整数范围[-2^(8*N-1) ~ 2^(8*N-1)]
。 -
date 日期类型。适用 3 个字节编码。
-
time 时间戳类型,毫秒部分会忽略。
-
boolean 适用 1 个字节表示布尔值。
维度顺序
HBase 中的数据按照 Rowkey 的字典排序顺序存储。
- 查询频率高的维度放在低频率之前
- 高基维度放在低基维度之前
分片维度 Shard By
Rowkeys 可以为一个(最多也只有一个)维度配置 Shard By
值为 true
。
Cube 在构建过程中需要 将中间表的数据分布到 HDFS 节点,默认 partition 方式是随机。如果指定分片维度,则使用分片维度做 partition。partition 的目的是防止中间表大小差异造成数据倾斜,因此 Shard By 维度应该使用高基维度,以使分片粒度足够小,能够加速之后的 MR 计算任务。
注意分片维度和 Cube 的 Partition 维度不同,前者作用于构建阶段,后者是用于分割时间段以支持增量构建
字典优化 Advanced Dictionaries
- Global Dictionary 用于精确计算 COUNT DISTINCT 的字典, 它会将一个非 integer 的值转成 integer,以便于 bitmap 进行去重。如果你要计算 COUNT DISTINCT 的列本身已经是 integer 类型,那么不需要定义 Global Dictionary。Global Dictionary 会被所有 segment 共享,因此支持在跨 segments 之间做上卷去重操作。请注意,Global Dictionary 随着数据的加载,可能会不断变大。
- Segment Dictionary 用于精确计算 COUNT DISTINCT 的字典,与 Global Dictionary 不同的是,它是基于一个 segment 的值构建的,因此不支持跨 segments 的汇总计算。如果你的 cube 不是分区的或者能保证你的所有 SQL 按照 partition_column 进行 group by, 那么你应该使用 “ Segment Dictionary ” 而不是 “ Global Dictionary ”,这样可以避免单个字典过大的问题。
- 字典复用 Reuse 如果 A 列是是 B 列的子集,则 A 列可以复用 B 列的字典。
列簇优化 Advanced ColumnFamily
Kylin 默认将所有度量放在 HBase 的 1 个列簇中。 每个 COUNT DISTINCT 度量使用 bitmap 存储明细数据,这样的度量基数一般都很大。多个这样的度量存储在同 1 个列簇计算量很大,可以将超过一个的 COUNT DISTINCT 或 TopN 度量, 放在更多列簇中(不同的列簇保存在不同的 Store 中),以优化与 HBase 的 I/O。 注意,HBase 的列簇最好控制在 2-3 个
Normal 普通列 VS Derived 派生列
在 LookupTable 可以设置列为 Normal
或者 Derived
。
当一个或多个维度能够从另一个维度(一般为事实表的外键)推导出来,可以考虑将这个列设置为派生列。派生列不会参与 Cube 计算。
New Model
中 New Join Condition
,左边为事实表的外键,右边为 lookup 表的主键
精确去重 Distinct Count
为了支持任意粒度的上卷聚合,精确去重需要保存明细数据,所以使用 Bitmap 保存精确去重度量数据。Kylin 使用 RoaringBitmap 库。 RoaringBitmap 只支持 Int 类型的数据,所以 Kylin 引入了全局字典,以保证在不同 Segment 上,String 到 Int 的映射一致。全局字典的核心数据结构是 AppendTrieDictionary。
近似去重
Kylin 的近似去重,基于 HLL ( HyperLogLog)实现。 简单来说,每个需要被计数的值都会经过特定 Hash 函数的计算,将得到的哈希值放入到 byte 数组中,最后根据特定算法对 byte 数据的内容进行统计,就可以得到近似的去重结果。
膨胀率 Expansion Rate
膨胀率是指 Cube 大小 /原始 Table 大小。膨胀率应该控制在 10 倍以内。 影响 Cube 大小的因素:
- Cube 维度数量很多,没有进行 Cuboid 剪枝优化,导致 Cuboid 数量巨大
- Cube 中存在高基维度,造成包含这类维度的 Cube 很大
- 存在占用空间很大的度量,比如 COUNT DISTINCT
超高基维度 UHC
UHC 代表 Ultra High Cardinality,即超高基数。基数表示维度不同值的数量。通常,维度的基数从数十到数百万。如果超过百万,我们将其称为超高基维度,例如:用户 ID,电话号码等。
参考文档
- Apache Kylin 优化之— Cube 的高级设置
- Kylin 实战(四):rowkey 调优
- Apache Kylin 精确去重和全局字典权威指南
- Apache Kylin 精确计数与全局字典揭秘