Apache Kylin基本原理与常见优化

一、基本原理

Apache Kylin是个开源分布式OLAP引擎,其基本原理是数据立方和预计算

Kylin一般是作为数据仓库的应用层引擎,对业务提供SQL查询分析服务,针对数据维度多、数据基数大的场景,Kylin预计算可以保证在毫秒级时间返回分析结果,查询阶段性能十分出众。

Kylin的相关人员主要分为三种角色:1. 数据用户 2. 数据仓库建模人员 3. 数据平台管理员。数据用户通过BI可视化分析工具或者编写SQL查询Kylin的数据; 数据仓库建模人员维护Kylin元数据,设计如何构建Cube,选择维度、度量;数据平台管理员提供存储和计算平台支持,目前最常见的存储引擎是HBase,计算引擎是Spark。

在这里插入图片描述

1. 基础模型:Star Schema

我们以电商场景为例具体看一下如何设计和构建Kylin Cube。下图是一个典型数据仓库中的星型模型,模型的中心是事实表 fact_sales,模型的周围是五张维度表:dim_date,dim_product, dim_store,dim_promotion,dim_customer,它们通过代理键和事实表相关联;

在这里插入图片描述

在这个星型模型下,有一个需求,“分析电商网站用户在一周的每一天中,更倾向于购买Fresh Fruit还是Candy”;

终端用户用SQL实现如下查询:

SELECT
dim_date.weekday, dim_product.category,
SUM(fact_sales.quantity) AS quantity_sold
FROM fact_sales
JOIN dim_date ON fact_sales.date_key = dim_date.date_key
JOIN dim_product ON fact_sales.product_sk = dim_product.product_sk
WHERE
dim_date.year = 2013 AND
dim_product.category IN ('Fresh fruit', 'Candy')
GROUP BY
dim_date.weekday, dim_product.category;

备注:Kylin也支持其他数据仓库模型,但大多数数据仓库场景都是星型模型;本文为了简单起见,不展开讨论数据仓库建模。

2. 基本原理:预计算

从最简单的情况开始,我们构建一个OLAP Cube,它只包含date_key和product_sk两个维度,如下图所示
在这里插入图片描述

假设存储引擎为HBase,物理上看,一个Cube的存储内容如下所示,其中RowKey是维度取值的拼接,Value是SUM(net_price);

Base Cuboid的RowKey SUM(net_price)
140101_32 149.60
140101_33 31.01
140101_34 84.58
140101_35 28.18
140102_32 132.18
140102_33 19.78
140102_34 82.91
140102_35 10.96

3. 列存储

在这里插入图片描述

简单说,HBase的表是rowkey有序的多层map,如果不熟悉HBase存储结构,推荐阅读这篇:深入理解HBase系统架构

4. 压缩编码

Kylin默认对所有维度列做压缩编码,编码格式一般是bitmap全局字典。如果某一个维度有特定的含义,可以手动指定编码类型,如date、int等。

在这里插入图片描述

二、Cube建模优化

Cube建模优化的核心是cuboid剪枝,避免不必要的维度组合,减少kylin预计算cuboid的数量;在不优化的情况下,一个n个维度的Cube存在2^(n)种维度组合,cuboid个数随着维度个数指数增长,很容易出现维度爆炸。

1. 维度

(1). 聚集组

含义:如果定义了聚集组G, 组内包含{A, B, C, …} 若干维度,那么在创建Cube时,只有组内维度组合的cuboid会被创建,组外维度不会预先创建cuboid

举例:例如cube定义了30个维度,定义了3个聚集组,每组包括10个维度。定义聚集组后,需要预计算的cuboid个数为 2^{10} + 2^{10} + 2{10}。如果没有定义聚集组,需要预计算2{30}个cuboid。

(2). 强制维度

含义:必选维度、所有查询都必须包含该字段,例如日期;

效果:假设聚集组中有n个维度,其中包含m个强制维度,那么cuboid个数从 2^(n)优化为 2^(n-m)个

举例:把日期字段dt作为强制维度,所有不包含dt的维度组合都不会被预计算,从而cuboid个数可以减半。

在这里插入图片描述

(3). 层次维度

含义:维度有上下层级关系,查询时一般按上下级维度进行“上卷”、“下钻”,例如组织架构,标签结构;

效果:假设有一组n个维度,定义层次维度前,cuboid个数是2^{n}, 把这n个维度定义成层次维度后,cuboid个数是n,即层次维度把指数增长变成线性增长;

举例:假如存在“省”,“市”,“县”,“街道”四个维度,定义了一个层次维度,包括“省”,“市”,“县”,“街道”,那么cuboid个数从2^{4} 减少为4个,分别是{“省”,“市”,“县”,“街道”}, {“省”,“市”,“县”}, {“省”,“市”}, {“省”} 。

在这里插入图片描述

(4). 联合维度

含义:如果定义了联合维度A, B, …, X, 那么Kylin预计算cuboid时会把{A,B,…,X}当做一个维度,他们或者同时出现,或者同时不出现;

效果:相当于把联合维度中的n个维度缩减成了一个维度,预计算量比定义联合维度前减少了2^{n-1}

举例:有两种常用场景: 第一种是xxx_id, xxx_name, 使用时按id查询,按name显示,这种情况适合把id和name作为一个联合维度;第二种是cube维度中, 存在一些低频查询维度X, Y, Z,这时候把X, Y, Z作为一个联合维度,可以减少预计算量。

(5). 衍生维度

含义:如果维度配置了lookup table, 某些维度可以通过主键字段衍生到固定的取值,可以给这些维度配置衍生维度

效果:假如衍生维度个数为n,则预计算cuboid的个数从2{n}减少到2{1}

举例:例如可以给电商销售事实表中的商品维度定义一个lookup table, 商品SK作为外键,这时商品的属性维度可以缩减到一个

在这里插入图片描述

2. 度量

(1). Dimension or Measure

首先,避免在cube中引入高基数维度,如果需求是对改字段进行去重,应该将该字段配置成Measure而不是dimension;

其次,实在需要按该字段过滤筛选,应该将字段配置成fixed_length编码;

(2). Extended Column

一个id,一个name,查询时按id匹配,显示时按name做展示,这种情况可以把name作为id的extended column。和联合维度类似,可以简单认为id, name从2个维度变成了1个维度。

3. Cube Planner

Cube Planner是Kylin 2.3版本推出的功能,它可以自动地对Cube结构进行优化。

Cube Planner优化分为两个阶段。第一阶段发生在初次构建Cube时:Cube Planner会利用在“Extract FactTable Distinct Columns”步骤中得到的采样数据,预估每个Cuboid的大小,进而计算出每个Cuboid的效益比(该Cuboid的查询成本/对应维度组合物化后对整个Cube的所有查询能减少的查询成本)。Cube Planner只会对那些效益比更高的维度组合进行预计算,而舍弃那些效益比更低的维度组合。第二阶段作用于已经运行一段时间的Cube。在这一阶段,Cube Planner会从System Cube中获取该Cube的查询统计数据,并根据被查询命中的概率给Cuboid赋予一定权重。当用户触发对Cube的优化操作时,那些几乎不被查询命中的Cuboid会被删除,而那些被频繁查询却尚未被预计算出的Cuboid则会被计算并更新到Cube中。

关于Cube Planner更多细节,请参考Kylin官方网站相关文档

三、HBase存储优化

Kylin是可插拔式架构,它依赖的存储引擎,计算引擎都是面向接口的,不依赖于特定引擎。开源版本的存储引擎是HBase,本文主要以HBase存储为例介绍常见的存储层面的优化思路。

1. rowkey顺序

前提:hbase存储, rowkey是多个字段拼接,hbase的基本原理是在相同region中rowkey有序;在kylin查询中,预计算结果按rowkey进行查找;

例子:rowkey取值d1_d2_d3,相同基数的情况下,按d1维度过滤的效率要高于d2, 按d2维度过滤的效率要高于d3;基数多的字段,能过滤掉更多数据量的字段,更值得放在rowkey的前面;

2. 维度编码

含义:用编码代替原始值,减少cube存储大小;根据字段基数选择相适应的编码类型;一般默认选择字典编码(dict),个别字段基数非常大(百万量级),可以替换成固定长度编码(fixed_length);

3. Shard by column

如果一列基数足够高,例如几百万,那可以把相同取值的数据在相同的hbase region中存储,提高存储本地性,从而提高计算效率;

四、OLAP引擎对比

讲道理,Apache Kylin是很有中国特色的一个开源项目,主要是指项目发起人、主要开发者都是中国人,因此也存在一些特有的问题,比如项目的英文文档读起来就不那么通顺,这一方面是英文写作行文语法的问题,另一方面是理论深度的问题。理论上的问题是项目的根基,决定了项目的生命力,项目能走多远很大程度上取决于它的理论基础是否牢靠。

1. Kylin和Doris

Doris是百度发起的项目,是另外一个国人主导的开源OLAP项目。Doris的前身是百度PALO,总体是参考Google Mesa项目进行搭建的。

Doris是一个MPP的OLAP项目,和Kylin的主要区别是它是查询时计算的引擎,同时利用索引提升性能。Doris不需要预计算,不需要数仓建模人员预定义模型,因此上手难度要比Kylin小很多。

和Doris相比,Kylin目前的代码扩展性更好,因为Kylin的整体设计是面向接口的,存储引擎,计算引擎,这些都是可更换的。例如,早期Kylin的代码是基于Hadoop的MapReduce计算引擎,基于HBase的存储引擎,但是随着大数据技术的发展,MapReduce实际在很多任务的性能上不如Spark,这时候Kylin把计算引擎更换成Spark,可以让Kylin持续保持竞争力。再比如,Kylin的存储引擎HBase,实际上在HBase Region数大到一定规模的时候,会遇到难以横向扩展的问题,这时候如果能做到存储引擎替换,对Parquet、ORC这些格式做一定的改造,或者基于Druid,基于Delta Lake等项目进行融合,这时候Kylin依然可以保持竞争力。Doris在这方面是分成前台和后台两大模块,从系统架构上来看,模块划分不够清晰,没有做到高内聚、低耦合。

2. Kylin和ES

我的工作经历是,数据量没那么大的时候,统统往ES里面录入,使用时写ES查询和聚合语句,流程很顺畅。ES的文档算得上业界良心了,入门文档上手非常快,上手后深入一些的文档也都能准确地说明要点,内容安排恰到好处,用户体验很不错。

但是ES也会遇到它的问题,就是用户需要从存储层重新设计结构。原理上讲,ES的本质是倒排索引,你查询的是索引而不是原始数据;这个存储模型导致了使用ES时你需要仔细设计索引的存储结构,尤其是关联文档,是用嵌套文档还是父子文档,一不小心,父子文档查询和聚合的性能可能就不满足你的需求了。另外,ES容易遇到全表扫描,集群假死的问题,需要想办法在应用层面规避用户的全表扫描。

3. Kylin和Druid

我之前的工作经历中,实际应用到Druid的项目并不多,没有太多实践经验可以分享,简单从概念上对比一下吧。Druid的核心思想是数据录入时做轻度汇总,查询时做计算。在预计算和MPP之间,Druid更接近MPP一些。和Kylin相比,它的数据延迟明显更低,因此Kylin主要是做离线计算,Druid在实时的场景中应用更多。另一方面,Druid在数据录入时做轻度汇总,这个汇总的粒度取决于用户指定的查询粒度,这个粒度可以选择毫秒/秒/分钟,等各种级别,看你的具体使用场景需求。

最后关注一下大家的共性,绝大多数OLAP项目都用到了列存储字典编码位图索引等技术。这是因为大数据项目中提前剪枝掉一部分数据就等于节约了大量的数据读取、中转、加工成本,能给系统带来巨大的性能提升。列存储是在剪枝掉用不到的列,字典编码更紧凑地存储了维度列。位图索引是在通过布尔运算快速同时匹配多个过滤条件,避免了对一份数据集多次过滤后再对过滤结果进一步执行布尔运算。

五、参考资料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章