如何實現海量數據下有序漏斗秒查

近期易觀公司舉辦了一個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/

相關文章
相關標籤/搜索