设计背景与动机

一般认为,OLAP可以分为三种实现方案:ROLAP (Relational OLAP),MOLAP (MutidimensionalOLAP)和HOLAP (Hybrid OLAP),ROLAP基于关系型模型实现,按照星型模型或雪花模型将数据建模成维度表和事实表,并将OLAP操作翻译成SQL到底层引擎中执行,这是一种非常经典的OLAP技术,常见的实现引擎可以是Presto,Impala,clickhouse等;MOLAP利用多维矩阵保存数据,将结果组织成数据立方体以达到空间换时间的加速效果,相比于ROLAP,查询性能更高,但占用存储空间过大,灵活性也较低,典型引擎有Druid、Doris、Kylin等;HOLAP则结合前两种方案的优缺点,能在性能、存储成本和灵活性之间折中,Kylin在一定程度上兼有HOLAP特性(可以优先查询cube,如果cube数据不足,可将查询路由到通用引擎中),但灵活性仍不足。

Sophon是Hulu在HOLAP的一个尝试。Hulu底层查询引擎采用Impala,前端可视化采用类似MSTR和Tableau这样的方案,而Sophon则是一个中间件,位于Impala查询系统和可视化系统之间,起到数据建模、数据缓存和数据路由等功能(具体可查看这篇文章)。本文重点讨论数据缓存部分的实现。

Sophon提供了复杂数据建模功能(比如层次建模、多维度多事实等),通常会有大量的SQL query查询了相似的度量指标。为了避免重复计算带来的高昂额外代价,可以引入类似于cache的机制。具体做法是建立一些聚集表(以下称为Aggregation),以预先计算并存储一些经常用到的计算结果。

这样的Aggregation可以手动定义,但由于数据模型的复杂性以及其Metadata的不断变化,手动定义的方式会带来极大的工作量。于是我们试图找到一种方法,可以根据历史上不同Query出现的频次,来决定一种创建Aggregation的较优方案。

算法原型

该问题是一个非常经典的优化问题,我们采用的算法思想一部分来源于Sigmod 1996上一篇关于datacube的论文。

这篇论文介绍了一个简单的问题模型:

假设一张表有四列part, supplier, customer, SP

一个示例的SQL语句是

SELECT Part, Customer, SUM(SP) AS Sales FROM Raw-data GROUP BY Part, Customer;

针对上面的SQL,改变GROUP BY的column,我们可以得到8种可能的组合:

定义一种偏序关系,当且仅当询问可以由偏序结果来回答。对于只有group by部分不同的query,我们使用它的grouping属性来标记它,比如上文中以Customer和Part作为grouping属性的SQL,我们用(Customer, Part)来标记。

比如(Part)(Part,Customer),而(Part)(Customer)则可由以上偏序求得。

按照这种偏序关系,可以画出上述例子的Hasse图:

假设每次询问的时间花费与数据表的行数成正比,在不物化(物化的英文为“materialize”,即构建生成aggregation结果并存储起来)任何表的情况下,每一个询问都需要在raw data中查,所以每次询问的时间都是6M。

如果物化(Part,Supplier)的结果,则(Part,Supplier)(Part)(Supplier)的结果都可以通过(Part,Supplier)的结果计算出来,而不需要去访问raw data,故而它们的查询时间减少到了0.8M。

在更一般的情形下,某些属性之间具有层级关系(比如表示时间的日月年),我们仍然可以根据他们的层级关系建出Hasse图。

问题优化目标

假设所有Query仅由groupby部分所区分,该论文中提出的算法的优化目标是

  • 选择最多K个query的结果作为计算aggregation(K为常量)的参考
  • 最小化所有询问的平均时长(此处可以根据query出现的评测加权)

这个问题可以从集合覆盖问题归约过来,所以它是NP-Complete的,但论文中提出了一种表现足够优秀的贪心算法。


贪心算法

贪心的思路是进行K次迭代,每次选择一个Query创建Aggregation,每次迭代时贪心的策略是选择能使当前平均询问时长下降最多的那个Query。

这个算法的时间复杂度为O(KN), N为哈斯图的规模。

论文中给出了数学推导,可以证明这种贪心算法所能减少的平均时长至少是最优解的(e-1)/e倍(e为自然对数的底数)。

实际应用时的问题

实际上,我们很难将上述论文中的算法投入实际应用,主要面临的问题有两个:

  • 上述算法的复杂度与哈斯图的规模线性相关,而哈斯图的规模随dimension数指数级增长,比如当dimension数达到20个时,哈斯图的规模最大便可达到2^20。这样的复杂度显然是无法接受的。
  • 我们的底层计算引擎是Impala,在Impala中,无法如论文中简单地去估计一个Query所需要的时间,也就难以估计创建Aggregation能带来的收益。

Query分组

动机

为了解决第一个问题,我们需要将实际问题划分成若干个小问题。我们显然不能直接对dimension集合进行划分,那怎么办呢?

考虑到我们的输入数据是不同Query出现的频次,我们实际上可以将Query Set划分成若干组,并使得每组中Query涉及的dimension尽量相近。这样,只要我们控制Query组涉及不同dimension的个数,就可以在可接受的时间内分别解决各个Query子集的问题。

这个将Query Set分组的问题很容易让人联想到图论问题:即将query看作图中的点,将相似关系看做图中的边,这个划分算法的目的即是将这张图分成若干个联系紧密的子图。

在若干尝试后,受到随机化算法求解最大团问题的启发,我设计了如下算法:

【算法描述】

算法进行多次迭代,每次从图中剥离出一个子图,直到剥离出的子图大小小于等于1,算法结束。

关键是如何找到当前迭代时应剥离出的子图。

我们希望每次找到一个当前图中的“最大团”,它具有如下性质:

  • 团中每个点至少与另外min{CONN, CliqueSize -1}个点有相似关系
  • 相似关系被定义为两个点所代表的Query有至少SIMI个公共dimension
  • 团中涉及到的不同dimension个数小于25

(其中CONN、SIMI均为常量,CliqueSize为“团”的大小)

考虑这样一种贪心算法,即从第一个点开始依次扫描每个点,若把当前点加入“团”中不会破坏它的性质,就将其加入“团”中,这样我们就可以在O(n^2)的时间内得到一个“极大团”。

这样的贪心算法得到的“极大团”的大小主要取决于点的扫描顺序,为了能求得一个较好的解,我们可以随机出大量(100000个以上)不同的扫描序列,并按照每个扫描序列分别做一次贪心,最后取其中的最优解(最大的“团”),即当前迭代应剥离的子图。

经过多次迭代之后,整张图即被划分成了若干个“团”与若干个孤立点。

实际问题中的费用模型

经过试验发现,Impala的Query时间主要与Fact表的大小和Join表的次数有关,于是可以利用这两个指标估算Query未命中Aggregation时的时间花费。

而对于命中Aggregation的Query,我们首先可以得到创建Aggregation的SQL语句,Impala可以根据这个语句去估算出Aggregation的行数,我们可以利用Aggregation的行数来计算Query命中Aggregation时的时间花费。

算法总结

  • 输入数据为一组Query,包含了Query的语句和出现频次
  • 使用类似随机化求最大团的方法将Query分组
  •  对于每一组Query应用论文中提出的贪心算法
  • 每次迭代时在所有Query组中选择收益最高的Aggregation
  • 迭代至选择了足够多的Aggregation或者估计Aggregation的占用空间已经达到上限时算法结束

几点补充

当单个Aggregation涉及的dimension很多时,构建Aggregation的时间成本可能会非常高,所以我加入了对单个Aggregation的最大Dimension数量限制。

同时,在贪心算法迭代的过程中,有可能出现在迭代初期选择的Aggregation所影响到的Query被其子Aggregation覆盖的情况,此时,这些Query选择从其子Aggregation计算结果显然会更优,故应将这种Aggregtion删除以避免不必要的时空消耗。

这个算法并没有考虑将Aggregation作为Query的中间结果,但这一feature在atscale(一个商用的OLAP数据中间件)和sophon(Hulu自研的OLAP数据中间件)里都有支持。但由于该情况过于复杂,而目前的结果来看该算法的表现已经可以接受,故暂不讨论此情况。

测试结果

完整的测试还在进行中,以下是一个简单的测试环境中的测试结果。

数据表采样

完整的拷贝线上环境中所有Dimension表,对Fact表进行1/10的随机采样。

输入数据集

输入数据为在线上环境中随机抽取一段连续时间内的1000条query。

测试数据集

测试数据集为在这1000条query中随机选取的17个query

测试结果

测试一共分为3个环境:

  • Baseline – 没有Aggregation
  • Aggr-v20 利用该算法创建20个Aggregation
  • Aggr-v50 利用该算法创建50个Aggregation

如上所示,在创建50个aggregation之后,几乎所有query的时间消耗都降到了秒或亚秒级别,平均时间消耗也被大大降低了。对于个别Query时间优化并不明显,我们还需进一步调优缓存策略和SQL执行策略。

参考资料

Implementing Data CubesEfficiently

作者简介:周尚彦,北大在校本科生,Hulu大数据团队实习生,目前实习工作为OLAP优化相关工作。 

原创文章,转载请注明: 转载自董的博客

本文链接地址: Hulu在OLAP场景下数据缓存技术实战

微信公众号:hadoop-123,专注于大数据技术分享,欢迎加入!

说点什么

avatar
  Subscribe  
提醒