如何实现海量数据下有序漏斗秒查

近期易观公司举办了一个OLAP大赛,咱们队伍很是荣幸地得到了第一名,成为本次比赛最大黑马。此篇文章主要分享一下咱们是如何解决有序漏斗秒查问题的html

比赛地址:2017易观OLAP算法大赛mysql

参赛状况: https://www.analysys.cn/media/detail/20018458/git

1. 题目分析

在以上示例场景下,咱们在易观提供的6亿测试数据集上,在4台UCloud云主机(16core,16G ram)机器下从24s优化到了0.5s。而在正式比赛的26亿数据集上,使用相同硬件环境,耗时1.6s。github

2. 解题分析:

题目描述的有序漏斗问题能够归结为 带滑动时间窗口的最左子序列问题, 好比咱们须要寻找,2017年7月份中,在3小时的时间窗口下, [A,B,C,D] 漏斗路径下的转化状况, 单个用户只能有 NULL , [a], [A,B], [A,B,C], [A,B,C,D] 五种转化结果,对应的漏斗深度咱们称之为level,在[A,B,C,D]漏斗路径下,level的取值能够有[0,1,2,3,4] 四个值,题目的要求即算出全部用户的知足条件下最大level汇总结果。算法

理解问题以后,咱们梳理了一下流程图:sql

咱们将问题解决分为5个步骤:数据库

  1. filter阶段 :根据时间区间和事件属性对数据进行过滤
  2. group阶段: 根据用户Id进行group汇总
  3. sort阶段: 按照时间进行排序
  4. algorithm aggregate 阶段: 带时间窗口的子序列搜索
  5. 合并结果

3. 数据库选型:

根据以上分析,须要filter,group,sort,aggregate等操做,数据库是必备的核心,而在OLAP领域,开源的数据库选型有不少,好比:mysql, druid, kylin hdfs + (hive,spark,presto),imapla, kudu etc。数组

但在这个场景下,结合以往对其余数据的深刻研究分析对比经验,咱们几乎坚决果断就选择了 ClickHouse (纵然它不支持udaf),,咱们相信ClickHouse是目前cpu领域最快的olap开源数据库,它最突出的优势就是快,若是你是第一次用,相信ClickHouse会让你感到很是惊艳。数据结构

ClickHouse 由俄罗斯Yandex开发,09年原型,12年生产可用,16年开源,目前最大的线上部署实例是 Yandex.Metrica: 472个节点,每秒处理2T数据,实时在线分析。ClickHouse 在OLAP上的查询性能很是彪悍,平均查询性能几乎是vertica的三倍。架构

ClickHouse不只速度快,它系统架构灵活,性能优越,代码优雅, 很是适合大数据下须要极致性能的应用场景。ClickHouse目前暂不支持UDAF,但不要紧,咱们能够经过修改源代码并从新编译来实现自定义AggregateFunction。

以上就是针对漏斗场景的代码修改状况,能够看出咱们只用了不到300行代码就为ClickHouse加入了漏斗计算(aggregate function path)的功能。

对比官方的presto + hdfs 方案实现的24s速度,使用ClickHouse以后,咱们在测试环境下跑的速度达到了8s。

下面开始咱们的正式优化过程

4. 按照用户ID分区:

咱们注意到,漏斗的计算中,每一个用户都是相互独立的,因此咱们能够将数据按照uid来分区,这样就将数据分散到了四台机器上,咱们能够分别向每一个数据库节点发送请求,而后将数据进行汇总,获得最终结果。

经过此次优化,咱们在测试环境下跑的速度达到了1.6s。

5. 以(uid,timestamp)做为primary key

ClickHouse中primary key表明了数据的组织排序方式

左边的以(timestamp,uid)为主键,时间是有序的,方便作时间精准过滤,但uid压缩率低;右边的以(uid,timestamp) 为主键,uid压缩率高,方便作uid group, 但对时间过滤支持不够好。

经过测试对比,咱们发现以 (uid,timestamp) 做为主键性能略快,查询时间达到了 1.4s。

6. 分组预排序

当咱们以 (uid,timestamp) 做为primary key后, 分组内的数据其实已经有序了, 咱们能够去掉代码中的sort方法,来提升性能,通过这个优化,查询时间达到了 0.9s。

7. 带时间窗口的子序列搜索优化

这里主要是用了一些 剪枝的策略,当咱们从左往右去搜索 a,b,c,d 漏斗的时候,咱们须要找到最大的深度,必须一直去搜索以a开头的子序列;但咱们从右往左搜索时, 咱们只要考虑比当前结尾更大的子串便可, 好比咱们找到了 a,b,c, 后面咱们只须要考虑以d结尾的串,这样减少了搜索的复杂度,查询时间达到了0.8s。

8. 数据结构优化

事件ID到数组下标Index的映射,咱们直接遍历了数组搜索,而不使用std::unordered_map, 由于在events数据量不大的状况下, 数组搜索O(1)比O(n)慢。

使用纯C++数组存储事件序列,不使用std::vector,去掉了vector的开销,灵活控制内存分配。

经此优化,查询时间达到了0.6s。

9. 部分压缩

数据库一般会对字段进行压缩,这样作节省了硬盘空间,但却浪费了cpu计算,为了提供性能,咱们对字段进行了部分压缩,经此优化,查询时间达到了最终的0.5s。

  • uid => 压缩
  • timestamp => 不压缩
  • event_id => 不压缩
  • event_name => 压缩
  • event_tag => 压缩
  • date => 不压缩

总结:

咱们已经将代码和PPT开源:
https://github.com/analysys/olap


0.5s固然不会是极限,现在GPU数据库大道横行,技术变革也愈演愈烈, 前路漫长,预测将来最好的方法就是本身创造将来。

Refer

[1] 如何实现海量数据下有序漏斗秒查

https://zhuanlan.zhihu.com/p/30823204

[2] analysys/olap

https://github.com/analysys/olap

[3] 易观olap大赛 

http://ds.analysys.cn/OLAP.html

[4] 易观OLAP算法大赛结果揭晓,开源组黑马放大招!

https://www.analysys.cn/media/detail/20018458/

相关文章
相关标签/搜索