对于互联网产品来说,UV 与 PV 是两个非常常见的指标,并且通常都是分析的最基础指标。UV 一般来讲,是指使用产品(或产品某个功能)的独立用户数。PV 则来源于网站时代,一般指网站(或网站某个页面)的页面浏览量,在移动互联网时代,则一般会引申表示使用产品(或产品某个功能)的用户行为或者用户操作数量。
而为了能够让多维分析发挥出更大的价值,一般情况下,都是希望多维分析的查询结果能够在一分钟能就得到,从而可以让使用者不停地调整查询条件,快速地验证自己的猜想。
正如上面提到的,多维分析对于查询速度非常敏感,业内也有很多专门的存储和查询方案。
而在具体的实现中,有一种最为常见的实现手段,就是把各个维度的所有取值组合下的指标全部预先计算并且存储好,这种一般可以称作事实表。然后在具体进行多维查询的时候,再根据维度的选择,扫描相对应的数据,并聚合得到最终的查询条件。
但是,对于 UV 这类指标,却不能简单的累加,因为,这个指标并不是在每一个维度上都是正交的。例如,同一个用户可能先后使用了不同的 App 版本,甚至于有一定几率使用了不同的终端,所以,UV 并不能简单地累加,通常情况下,真实的 UV 是比加起来的值更小的。
因而,对于 UV 这类不可累加的指标,需要使用其它的计算方案。
UV 类型的指标,有三种常见的计算方案,我们在这里分别进行介绍。
所谓的估算方案,就是在上面的表格的基础上,不再额外记录更多细节,而是通过估算的方式来给出一个接近真实值的 UV 结果,常见的算法有很多,例如 HyperLogLog 等。
由于毕竟是估算,最终估算的结果有可能与真实值有较大差异,因此只有一些统计平台可能会采用,而如我们 Sensors Analytics 之类的以精细化分析为核心的分析系统并不会采用,因此在这里不做更多描述。
ClickHouse提供各种各样在允许牺牲数据精度的情况下对查询进行加速的方法:
- 用于近似计算的各类聚合函数,如:distinct values, medians, quantiles
- 基于数据的部分样本进行近似查询。这时,仅会从磁盘检索少部分比例的数据。
- 不使用全部的聚合条件,通过随机选择有限个数据聚合条件进行聚合。这在数据聚合条件满足某些分布条件下,在提供相当准确的聚合结果的同时降低了计算资源的使用。
MOLAP(Multidimensional OLAP,多维型OLAP): 其核心思想是借助预先聚合结果,使用空间换取时间的形式最终提升查询性能。
所谓以存代算,就是在预先计算事实表的时候,将所有需要聚合的结果,都算好。
依然以上面的例子来说明,如果我们想以存代算,预先做完聚合,类似于 Hive 所提供的group by with cube操作。
缺点:
- 无法进行多选,除非把多选也作为维度,有维度爆炸的问题。
- 预处理的形式,数据立方体会有一定的滞后性,不能实时进行数据分析。
(1)由于预先聚合只能支持固定的分析场景,所以它无法满足自定义分析的需求。
(2)维度组合爆炸会导致数据膨胀,这样会造成不必要的计算和存储开销。因为用户并不一定会用到所有维度的组合,那些没有被用到的组合将会成为浪费的开销。
(3)流量数据是在线实时接收的,所以预聚合还需要考虑如何及时更新数据。
ROLAP(Relational Online Analytical Processing),从最细粒度数据上扫描
之前提出的扩充事实表的方式,的确可以解决多维分析中指标聚合的问题,除此之外,还有一种方案,则是在事实表上,将用户ID也做为一个维度,来进行保存,此时就不需要保存 UV 了
虽然这样一来,需要保存的数据规模有了数量级上的扩充,并且所有的聚合计算都需要在多维分析查询的时候再扫描数据并进行聚合,存储和计算代价都提高了很多,看似是一种很无所谓的举措。
但是,相比较之前的方案,它却有一个最大的好处,也即是因为有了最细粒度的用户行为数据,才有可能计算事件级别的漏斗、留存、回访等,才有可能在这些数据的基础之上,进一步做用户画像、个性化推荐等等。
缺点:
- 数量级上的扩充,实时扫描聚合代价更高。
优点:最细粒度的用户行为数据。
在这样一个数据存储方案的基础上,为了提高数据查询的效能,一般的优化思路有:
- 采用列存储加压缩、位图等方式减少从磁盘中扫描的数据量,
- 采用分布式的方案提高并发扫描的性能,
- 采用应用层缓存来减少不同查询的公共扫描数据的量等等。
位图的使用约束: 不适用于对于维度去重率不高的表设计。
对于uniqExact(style_id, campaign_id, area)去重的基数来说,仅仅是当天总行数的85%。对于将这些维度爆炸来进行预聚合操作没有太大的意义,原因如下:
-
第一位图的运算在行数过大的情况下,比单纯的聚合会更慢。
-
第二位图的空间成本其实也是更大的,行数过大甚至会导致会比旧的数据更大。
现在社区已经有一些 OLAP 实时分析的工具,像 Druid 和 ClickHouse;目前快手采用的是 Flink+Kudu 的方案,在前期调研阶段对这三种方案从计算能力、分组聚合能力、查询并发以及查询延迟四个方面结合实时多维查询业务场景进行对比分析:
- 计算能力方面:多维查询这种业务场景需要支持 Sum、Count 和 count distinct 等能力,而 Druid 社区版本不支持 count distinct,快手内部版本支持数值类型、但不支持字符类型的 count distinct;ClickHouse 本身全都支持这些计算能力;Flink 是一个实时计算引擎,这些能力也都具备。
- 分组聚合能力方面:Druid 的分组聚合能力一般,ClickHouse 和 Flink 都支持较强的分组聚合能力。
- 查询并发方面:ClickHouse 的索引比较弱,不能支持较高的查询并发,Druid 和 Flink 都支持较高的并发度,存储系统 Kudu,它也支持强索引以及很高的并发。
- 查询延迟方面:Druid 和 ClickHouse 都是在查询时进行现计算,而 Flink+Kudu 方案,通过 Flink 实时计算后将指标结果直接存储到 Kudu 中,查询直接从 Kudu 中查询结果而不需要进行计算,所以查询延迟比较低。
以 UV 类指标计算举例,两个黄色虚线框分别对应两层计算模块:全维计算和降维计算。
全维计算分为两个步骤,为避免数据倾斜问题,首先是维度打散预聚合,将相同的维度值先哈希打散一下。因为 UV 指标需要做到精确去重,所以采用 Bitmap 进行去重操作,每分钟一个窗口计算出增量窗口内数据的 Bitmap 发送给第二步按维度全量聚合;在全量聚合中,将增量的 Bitmap 合并到全量 Bitmap 中最终得出准确的 UV 值。然而有人会有问题,针对用户 id 这种的数值类型的可以采用此种方案,但是对于 deviceid 这种字符类型的数据应该如何处理?实际上在源头,数据进行维度聚合之前,会通过字典服务将字符类型的变量转换为唯一的 Long 类型值,进而通过 Bitmap 进行去重计算 UV。
降维计算中,通过全维计算得出的结果进行预聚合然后进行全量聚合,最终将结果进行输出。