Kylin在使用的过程中使用hive作为cube的输入,但是有些情况下,hive中的表定义和数据并不能满足分析的需求,例如有些列的值需要进行处理,有些列的类型不满足需求,甚至有时候在创建hive表时为了图方便,hive中的所有列都被定义成了string,因此很多情况下在使用Kylin之前需要对hive上的数据格式进行适当的修剪,但是使用alter table的方式修改hive原始的schema信息未免会对其它依赖hive的组件有所影响(例如可能导致数据导入失败),于是不得不另辟蹊径,而此时使用hive的视图就是一个非常好的方案。
当然,除了Hive数据源本身schema的限制之外,Kylin对于hive的使用还有一定的限制,这也间接的导致我们需要使用视图:
1、同一个项目下使用相同表(可能根据不同的filter条件过滤,或者设置了不同的维度等)创建了不同的cube,会导致查询的时候定位到错误的cube。
2、只支持星状模型
Hive目前只支持逻辑视图,而我们需要的仅仅是对Hive原始的schema信息的修改,而并非希望通过物化视图优化查询速度(当然如果有就更好了~),因此目前Hive对视图的支持可以满足Kylin的需要。下面根据不同的场景分别介绍一下如何创建视图作为Kylin的输入源。
1、分表的情况:
两个表具有相同的结构,但是保存不同的数据,例如一个表保存Android端的访问数据,一个访问IOS端的数据,可以通过view搞定。
例如在我们的环境中有一个用户有两张表product_android和product_ios,这两个表具有相同的表结构,用户需要将平台(android或者IOS)作为一个维度进行分析,因此我们为其创建了这样的view:
create view product_cube as
select userid, eventid, label, day, ‘android’ as platform from product_android WHERE category=’a’
UNION ALL
select userid, eventid, label, day, ‘ios’ as platform from product_ios WHERE category=’a’;
这样可以将product_cube作为事实表创建cube而platform作为其中的一个维度
2、自定义函数,
Apache Calcite是支持自定义函数的,但是Kylin支持自定义函数的代价比较大,因此如果需要使用自定义函数,可以在Hive中创建view得到每一个希望的字段。
3、雪花状模型的支持,
目前kylin仅支持星状模型,而通过创建view可以轻易地把雪花状模型转换成星型模型,甚至生成一个大宽表。
4、频繁修改表字段名,
Kylin直接使用hive中的字段名作为元数据,如果频繁修改事实表或者维度表的字段名会导致元数据错误([https://issues.apache.org/jira/browse/KYLIN-1173]),因此通过view增加一层映射是比较好的方法,这样可以使得原生的hive表的字段名对Kylin的cube透明,此后再需要修改字段名的时候不会对cube有所影响,只需要修改view的定义。
5、UHC维度
,当一个维度的cardinality比较大时可能会出现的问题比较多,首先可能导致extract fact columns这一步时导致reducer出现OOM,其次在创建维度字典树可能会导致维度字典树太大占据大量的内存,另外会导致cube的构建速度缓慢,占用大量的存储空间,此时就需要思考一下这样的UHC维度是否是必须的,是否可以提取出部分信息减小维度,例如timestamp维度,是否可以精确到5分钟,详细地址的维度,是否可以精确到县、镇,这样可以大大减小维度数,而且更详细的信息并没有太大的统计意义。例如url类型的维度,是否可以把参数去掉只保留访问路径。在我们的实践中,如果用户有基于细粒度的时间统计的需求,我们会建议将时间规整到5分钟或者1分钟,创建view的时候使用如下的方式得到时间列:from_unixtime((unix_timestamp(wanproxy_stat.ts) - unix_timestamp(wanproxy_stat.ts) % 300), ‘yyyy-MM-dd HH:mm:ss’) as ts
6、维度作为度量,
在我们创建的cube中有一个例子是这样的,表中字段有时间、位置、速度,其中时间和位置是维度,速度是度量,类型是double,用户需要统计根据每一个维度或者维度组合中平均速度小于M并且小于N的平均值,这里就需要根据度量进行过滤,因此还需要将速度作为维度,但是速度的cardinality实在太大,因此我们将速度取整之后将其按5取整,这样的话,就可以大大降低这一个维度的取值。例如我们的一个用户有根据速度进行过滤的需求,那么我们为其创建了如下的维度列用于速度的过滤:
floor(cast(wanproxy_stat.datasize / wanproxy_stat.uploaderusetime as int) / 5) * 5 as velocity
而原始的速度列cast(wanproxy_stat.datasize / wanproxy_stat.uploaderusetime as int) as speed直接作为度量,那么在查询的时候可以执行这样的sql:
select ts, sum(speed) / count(1) from table where velocity < N and velocity > M group by ts;
7、hive表中类型修改,
有时候在创建hive表时为了方便,把所有的表字段都定义成string,但是calcite中对类型的限制比较严格,例如extract函数只能对date之类的类型进行操作,所以有时在定义cube之前需要对hive表中的字段进行转换,创建view就是一个很好的办法。
8、复合数据类型处理,
由于hive中可以定义复杂的数据类型,例如map、struct,而Kylin需要看到的是一个扁平的表结构,所以需要将复杂类型字段进行拆分出维度和度量。
在我们目前的实践中,每一个cube依赖的事实表都是通过view创建的,这样增加了一层映射,可以减小cube对原始表的依赖,提高灵活性,当然在查询性能上并没有找到相关的资料说明hive的view性能较差,在我们的实际使用过程中,并没有发现使用view对Kylin构建cube过程中速度有明显的影响。
由于hive的限制使用view时需要注意的问题:
1、由于hive不能对view使用HCatalog获取数据([https://issues.apache.org/jira/browse/HIVE-10851]),因此当你load一个view的时候Kylin计算表的cardinality的job通常会失败([https://issues.apache.org/jira/browse/KYLIN-916]),这时就要求用户知道每一列的cardinality大致的情况,如果实在不确定可以到hive里面查询一下(可以只查询一段时间的数据进行估算)。
2、一般情况下,我们创建cube的时候会指定一个分区字段(假设该字段为day),Kylin利用这个字段来增量计算每日的新数据,而对于需要增量计算的cube,在星状模型中一定有一个表中存在日期字段,一般为事实表,并且这个日期字段是hive表的分区键之一,但是对于日志数据在每一行数据中通常还会记录下该日志的时间(时间戳),一般是timestamp,如果此时需要时间作为维度,假设日期作为其中一个维度,那么就需要创建view将表中时间戳转换成日期,并将其作为维度,此时view已经存在了一个日期数据,但是建议用户还是要将分区的时间字段(day字段)放入到定义的view中,并且将其作为cube的分区字段,这样可以大大加快第一步的执行速度!(扫一个分区和扫整个表)。
那么既然有了分区字段表示日期是否只保留Hive的分区字段而不转换时间戳字段了呢?不可以的,因为分区字段只是标识了一个分区,它的值和时间戳中日值并不一定是相等的关系,虽然通常情况下是相同的,也不能保证某一个分区下的所有时间戳字段的值都是这个日期的(在我们使用的hive表中的确存在这样的情况)。例如在2016-01-15这个分区中可能前100条数据的时间戳是2014-01-14 23:59:59.xxx,所以你需要在view中保持这两个字段,一个作为度量(通过表中timestamp字段转换,参见场景5),一个作为cube的分区字段。
在我们的使用Kylin的过程中遇到了这些问题可以很好的通过Hive的视图进行解决,当然还会有一些需求通过视图能够很好的实现而不再需要大动干戈的修改hive表或者Kylin的元数据,如果有什么其他的场景还请多多补充。