编者按:本文节选自华章科技大数据技术丛书 《Apache Kylin 权威指南(第 2 版)》一书中的部分章节。
Top_N 度量优化
在生活中我们总能看到“世界 500 强公司” “销量最好的十款汽车”等标题的新闻报道,Top_N 分析是数据分析场景中常见的需求。在大数据时代,由于明细数据集越来越大,这种需求越来越明显。在没有预计算的情况下,得到一个分布式大数据集的 Top_N 结果需要很长时间,导致点对点查询的效率很低。
Kylin v1.5 以后的版本中引入了 Top_N 度量,意在进行 Cube 构建的时候预计算好需要的 Top_N,在查询阶段就可以迅速地获取并返回 Top_N 记录。这样,查询性能就远远高于没有 Top_N 预计算结果的 Cube,方便分析师对这类数据进行查询。
注意 这里的 Top_N 度量是一个近似的实现,如你想要了解其近似度,需要在本节之后的内容中更多地了解 Top_N 背后的算法和数据分布结构。
让我们用 Kylin 中通过 sample.sh 生成的项目“learn_kylin” 对 Top_N 进行说明。我们将重点使用其中的事实表“kylin_sales”。
这张样例表 “default.kylin_sales” 模拟了在线集市的交易数据,内含多个维度和度量列。这里仅用其中的四列即可:“PART_DT” “LSTG_SITE_ID” “SELLER_ID”和“PRICE”。表 3-1 所示为这些列的内容和基数简介,显而易见 “SELLER_ID” 是一个高基列。
表 3-1 样例表 “default.kylin_sales”的情况
假设电商公司需要查询特定时段内,特定站点交易额最高的 100 位卖家。查询语句如下:
方法 1:在不设置 Top_N 度量的情况下,为了支持这个查询,在创建 Cube 时设计如下:定义“PART_DT”“LSTG_SITE_ID”“SELLER_ID”作为维度,同时定义 “SUM(PRICE) ”作为度量。Cube 构建完成之后,Base Cuboid 如表 3-2 所示。
表 3-2 Cube 的 Base Cuboid 结构
假设这些维度是彼此独立的,则 Base Cuboid 中行数为各维度基数的乘积:730 * 50 * 1 million = 36.5 billion = 365 亿。其他包含“SELLER_ID”字段的 Cuboid 也至少有百万行。由此可知,由“SELLER_ID”作为维度会使得 Cube 的膨胀率很高,如果维度更多或基数更高,则情况更糟。但真正的挑战不止如此。
我们可能还会发现上面那个 SQL 查询并不能正常执行,或者需要花费特别长的时间,原因是这个查询拥有太大的在线计算量。假设你想查 30 天内 site_00 销售额排前 100 名的卖家,则查询引擎会从存储引擎中读取约 3000 万行记录,然后按销售额进行在线排序计算(排序无法用到预计算结果),最终返回排前 100 名的卖家。由于其中的关键步骤没有进行预计算,因此虽然最终结果只有 100 行,但计算耗时非常长,且内存和其中的控制器都在查询时被严重消耗了。
反思以上过程,业务关注的只是销售额最大的那些卖家,而我们存储了所有的(100 万)卖家,且在存储时是根据卖家 ID 而不是业务需要的销售额进行排序的,因此在线计算量非常大,因而此处有很大的优化空间。
方法 2:为了得到同一查询结果。如果在创建 Cube 时,对需要的 Top_N 进行了预计算,则查询会更加高效。如果在创建 Cube 时设计如下:不定义“SELLER_ID”为维度,仅定义“PART_DT”“LSTG_SITE_ID”为维度,同时定义一个 Top_N 度量,如图 1 所示。
图 1 TOP\_N 度量的设置示例
注意 “PRICE”定义在“ORDER|SUM by Column”,“SELLER_ID”定义在“Group by Column”。
新 Cube 的 Base Cuboid 如表 3-3 所示,Top_N 度量的单元格中存储了按 seller_id 进行聚合且按 sum(price) 倒序排列的 seller_id 和 sum(price)的组合。
表 3-3 新 Cube 的 Base Cuboid 结构
现在 Base Cuboid 中只有 730 * 50 = 36500 行。在度量的单元格中,预计算的 Top_N 结果以倒序的方式存储在一个容器中,而序列尾端的记录已经被过滤掉。
现在,对于上面那个 Top_100 seller 的查询语句,只需要从内存中读取 30 行,Kylin 将会从 Top_N Measure 的容器中抽出“SELLER_ID”和“SUM(PRICE)”,然后将其返回客户端(并且是已经完成排序的)。现在查询结果就能以亚秒级返回了。
一般来说,Kylin 在 Top_N 的单元格中会存储 100 倍的 Top_N 定义的返回类型的记录数,如对于 Top100,就存储 10000 条 seller00xxxx:xx.xx 记录。这样一来,对于 Base Cuboid 的 Top_N 查询总是精确的,不精确的情况会出现在对于其他 Cuboid 的查询上。举例来说,对于 Cuboid[PART_DT],Kylin 会将所有日期相同而站点不同的 TOP_N 单元格进行合并,这个合并后的结果会是近似的,尤其是在各个站点的前几名卖家相差较多的情况下。比如,如果 site_00 中排第 100 名的卖家在其他站点中都排在第 10000 名以后,那么它的 Top_N 记录在其他站点都会被舍去,Kylin 在合并 TOP_N Measure 时发现其他站点里没有这个卖家的值,于是会赋予这个卖家在其他站点中的 sum(price)一个估计值,这个估计值既可能比实际值高,也可能比实际值低,最后它在 Cuboid[PART_DT]中的 Top_N 单元格中存储的是一个近似值。
图书简介:https://item.jd.com/12566389.html
相关阅读:
Apache Kylin权威指南(五):Getting Started
Apache Kylin权威指南(六):Cuboid剪枝优化
评论