优化Cube
层次结构
理论上,对于N维,你最终会得到2 ^ N维组合。但是对于某些维度组,不需要创建这么多组合。例如,如果您有三个维度:洲,国家,城市(在层次结构中,“更大”维度首先出现)。在深入分析时,您只需要以下三种组合组合:
按大陆分组
按大陆,国家分组
按大陆,国家,城市分组
在这种情况下,组合计数从2 ^ 3 = 8减少到3,这是一个很好的优化。 YEAR,QUATER,MONTH,DATE案例也是如此。
派生列
派生列用于一个或多个维度(它们必须是查找表上的维度,这些列称为“派生”)可以从另一个维度推导出来(通常它是相应的FK,这称为“主机列”)
例如,假设我们有一个查找表,我们将其连接到事实表,并将其与“其中DimA = DimX”。请注意,在Kylin中,如果您选择FK为维度,相应的PK将自动排队,无需任何额外费用。秘诀是,由于FK和PK总是相同的,Kylin可以先在FK上应用过滤器/ groupby,然后将它们透明地替换为PK。这表明如果我们想在我们的立方体中使用DimA(FK),DimX(PK),DimB,DimC,我们可以安全地选择DimA,DimB,DimC。
事实表(连接)查找表
column1,column2 ,,,,,, DimA(FK)DimX(PK),, DimB,DimC
假设DimA(代表FK / PK的维度)具有到DimB的特殊映射:
dimA dimB dimC
1 a ?
2 b ?
3 c ?
4 a ?
在这种情况下,给定DimA中的值,确定DimB的值,因此我们说dimB可以从DimA导出。当我们构建一个包含DimA和DimB的多维数据集时,我们简单地包含DimA,并将DimB标记为派生。派生列(DimB)不参与长方体生成:
原创组合:
ABC,AB,AC,BC,A,B,C
从A到B时的组合:
AC,A,C
在运行时,如果查询类似于“select count(*) from fact_table inner join looup1 group by looup1 .dimB”,则期望包含DimB的长方体来回答查询。但是,由于派生优化,DimB将出现在NONE的长方体中。在这种情况下,我们首先修改执行计划以使其由DimA(其主机列)进行分组,我们将得到如下的中间答案:
DIMA COUNT(*)
1 1
2 1
3 1
4 1
之后,Kylin将用DimB值替换DimA值(因为它们的值都在查找表中,Kylin可以将整个查找表加载到内存中并为它们构建映射),并且中间结果变为:
DimB count(*)
a 1
b 1
c 1
a 1
在此之后,运行时SQL引擎将进一步将中间结果聚合为:
DimB count(*)
a 2
b 1
c 1
这一步发生在查询运行时,这意味着“以额外的运行时聚合为代价”
性能优化
分区列优化
如果cube的分区列与Hive表的分区列相同,那么根据它过滤数据能让Hive聪明地跳过不匹配的分区。因此强烈建议用Hive的分区列(如果它是日期列)作为cube的分区列。这对于那些数据量很大的表来说几乎是必须的,否则Hive不得不每次在这步扫描全部文件,消耗非常长的时间。
文件合并
如果启用了Hive的文件合并,你可以在conf/kylin_hive_conf.xml里关闭它,因为Kylin有自己合并文件的方法(下一节):
重新分发中间表
Hive在HDFS上的目录里生成了数据文件:有些是大文件,有些是小文件甚至空文件。这种不平衡的文件分布会导致之后的MR任务出现数据倾斜的问题:有些mapper完成得很快,但其他的就很慢。针对这个问题,Kylin增加了这一个步骤来“重新分发”数据,这是示例输出:
total input rows = 159869711
expected input rows per mapper = 1000000
num reducers for RedistributeFlatHiveTableStep = 160
重新分发表的命令:
hive -e "USE default;
SET dfs.replication=2;
SET hive.exec.compress.output=true;
SET hive.auto.convert.join.noconditionaltask=true;
SET hive.auto.convert.join.noconditionaltask.size=100000000;
SET mapreduce.job.split.metainfo.maxsize=-1;
set mapreduce.job.reduces=160;
set hive.merge.mapredfiles=false;
INSERT OVERWRITE TABLE kylin_intermediate_airline_cube_v3610f668a3cdb437e8373c034430f6c34 SELECT * FROM kylin_intermediate_airline_cube_v3610f668a3cdb437e8373c034430f6c34 DISTRIBUTE BY RAND();"
首先,Kylin计算出中间表的行数,然后基于行数的大小算出重新分发数据需要的文件数。默认情况下,Kylin为每一百万行分配一个文件。在这个例子中,有1.6亿行和160个reducer,每个reducer会写一个文件。在接下来对这张表进行的MR步骤里,Hadoop会启动和文件相同数量的mapper来处理数据(通常一百万行数据比一个HDFS数据块要小)。如果你的日常数据量没有这么大或者Hadoop集群有足够的资源,你或许想要更多的并发数,这时可以将conf/kylin.properties
里以下配置设为小一点的数值,比如:
kylin.job.mapreduce.mapper.input.rows=500000
其次,Kylin会运行 “INSERT OVERWRITE TABLE … DISTRIBUTE BY “ 形式的HiveQL来分发数据到指定数量的reducer上。在很多情况下,Kylin请求Hive随机分发数据到reducer,然后得到大小相近的文件,分发的语句是”DISTRIBUTE BY RAND()”。
如果你的cube指定了一个高基数的列,比如”USER_ID”,作为”分片”维度(在cube的“高级设置”页面),Kylin会让Hive根据该列的值重新分发数据,那么在该列有着相同值的行将被分发到同一个文件。这比随机要分发要好得多,因为不仅重新分布了数据,并且在没有额外代价的情况下对数据进行了预先分类,如此一来接下来的cube build处理会从中受益。在典型的场景下,这样优化可以减少40%的build时长。在这个案例中分发的语句是”DISTRIBUTE BY USER_ID”:
请注意: 1)“分片”列应该是高基数的维度列,并且它会出现在很多的cuboid中(不只是出现在少数的cuboid)。 使用它来合理进行分发可以在每个时间范围内的数据均匀分布,否则会造成数据倾斜,从而降低build效率。典型的正面例子是:“USER_ID”、“SELLER_ID”、“PRODUCT”、“CELL_NUMBER”等等,这些列的基数应该大于一千(远大于reducer的数量)。 2)”分片”对cube的存储同样有好处,不过这超出了本文的范围。
将cuboid数据转换为HFile
这一步启动一个MR任务来讲cuboid文件(序列文件格式)转换为HBase的HFile格式。Kylin通过cube统计数据计算HBase的region数目,默认情况下每5GB数据对应一个region。Region越多,MR使用的reducer也会越多。如果你观察到reducer数目较小且性能较差,你可以将“conf/kylin.properties”里的以下参数设小一点,比如:
kylin.hbase.region.cut=2
kylin.hbase.hfile.size.gb=1
rowkey构建
对rowkey的构建也有一定的要求,一般而言,需要把基数大的字段放在前面,这样可以在scan的过程中尽可能的跳过更多的rowkey。
另一方面将基数小的列放在rowkey的后面,可以减少构建的重复计算,有些cuboid可以通过一个以上的父cuboid聚合而成,在这种情况下,Kylin将会选择最小的父cuboid。例如,AB能够通过ABC(id:1110)和ABD(id:1101)聚合生成,因此ABD会被作为父cuboid使用,因为它的id比ABC要小。基于以上处理,如果D的基数很小,那么此次聚合操作就会花费很小的代价。因此,当设计cube的rowkey顺序的时候,请记住,将低基数的维度列放在尾部。这不仅对cube的构建过程有好处,而且对cube查询也有好处,因为后聚合(应该是指在HBase查找对应cuboid的过程)也遵循这个规则。
数据转换为HFile
kylin将生成的cube通过生成HFile的方式导入到hbase,这个优化点可以配置hbase的相关参数。
- region数量默认是1,如果数据量大的话可以提高region数量
- region大小默认是5GB,也就是hbae官方建议的大小;如果cube大小比这个值小太多,可以减小单region的大小
- hfile文件大小,默认是1GB,由于是通过mapreduce写入的,小文件意味着写入快,但是读取慢,大文件意味着写入慢,读取快
经验
- 尽量将需要展现的字段作为维度,没必要所有的一股脑加进去。
- 每次查询或者要经常group by的字段作为Mandatory维度。且该维度放在 rowkey的最前面。
- 将数量相近也就是说某两个字段通过select count("字段名")获取的结果近似1:1,设置为joint维度。
- rowkey的顺序按查询频率从高到低,从前往后排。
- 将经常出现在同一SQL中的不同维度放置在一个维度组中,将从不出现在一个SQL查询中的不同维度设置在不同的维度组中。
- Dictionary默认为dict类型,如果某个字段中的值非常大(小幽遇到过的一个字段中的值保存成文本足足有23Kb!!!),大到以至或者可能使得Cube在build过程中出现OOM的错误,则需要将该字段的值设置为fixed_length类型,取可以展现这个维度的前length个字节,比如对于之前那个23kb的字段值,经和业务人员协商,发现取前4000个字节就可以表示这个字段了。所以fixed_length的值设置为4000.值得一提的是,Dictionary默认为false,是不给该字段在内存中建立词典树的,而更改为true则表示给该字段建立词典树。有词典树,则会优化带有该字段的SQL查询,提升查询速度,但相应地也会消耗一些内存。
总结
基于kylin的ui,可以看到kylin在构建cube时各个流程的耗时,可以依据这些耗时做相应的优化,常见的,可以从耗时最长的步骤开始优化,比如:
- 遇到创建hive中间表时间很长,考虑对hive表进行分区处理,对表中的文件格式更改,使用orc,parquet等高性能的文件格式
- 遇到cube构建时间过长,查看cube设计是否合理,维度的组合关系是否可以再减少,构建引擎是否可以优化
分享一个其他得cube优化设计的推荐:https://www.cnblogs.com/wenBlog/p/10255467.html