天天看點

Clickhouse 專欄---資料模型之留存分析

什麼是留存,比如在20200701這天操作了“點選banner”的使用者有100個,這部分使用者在20200702這天操作了“點選app簽到”的有20個,那麼對于分析時間是20200701,且“點選banner”的使用者在次日“點選app簽到”的留存率是20%。

背景

關于使用者留存模型是各大商業資料分析平台必不可少的功能,企業一般用該模型衡量使用者的活躍情況,也是能直接反應産品功能價值的直接名額;如,boss想要了解商城改版後,對使用者加購以及後續下單情況的影響等。如下圖,這就是一個典型的留存分析功能:

Clickhouse 專欄---資料模型之留存分析

問題

通常實作上述需求的傳統做法是多表關聯,了解clickhouse的攻城獅都清楚,多表關聯簡直就是clickhouse的天敵;如一張使用者行為日志表中至少包含:使用者id、行為事件、操作時間、地點屬性等,想分析20200909日河南省注冊使用者次日的下單情況,那麼SQL一般會這麼寫:

select count(distinct t1.uid) r1, count(distinct t2.uid) r2 from
(
  select uid from action_log where day='20200909' and action='login' and province='河南省'
) as t1 left join
(
  select uid from action_log where day='20200910' and action='order' and  province='河南省'
) as t2
using uid
           

這種方式書寫簡單、好了解,但是性能會很差,在超大資料集上進行運算是不僅僅影響使用者體驗,還會因長期占有實體資源而拖垮整個clickhouse上的業務。

解決方法有兩種:

  • 使用clickhouse自帶的retention函數
  • Roaringbitmap 通過對資料進行壓縮和位運算提高查詢性能

Roaringbitmap

通過Roaringbitmap進行使用者行為分析是騰訊廣告業務中常用的一種實作方案,點選檢視 ,文章中内容較多這裡挑選幹貨進行講解:

bitmap可以了解為一個長度很長且隻存儲0/1數字的集合,如某個使用者通過特定的雜湊演算法映射到位圖内時,那麼該位置就會被置為1,否則為0;通過這種方式對資料進行壓縮,空間使用率可提示數十倍,資料可以很容易被系統cache,大大減少了IO操作。

在查詢之前需要先對資料進行預處理,這裡額外建構兩張表,用來存儲使用者的位圖資訊。

  • 使用者行為日志表:table_oper_bit
Clickhouse 專欄---資料模型之留存分析

向位圖表插入資料,原始資料十幾億,插入後結果隻有幾萬行,而且随着資料範圍的再擴大,位圖表的資料增量變化也不會很明顯。

Clickhouse 專欄---資料模型之留存分析
  • 使用者基本資訊表:table_attribute_bit 
Clickhouse 專欄---資料模型之留存分析

同理table_attribute_bit插入後資料也得到了極大的壓縮,最終資料如下圖:

Clickhouse 專欄---資料模型之留存分析
Clickhouse 專欄---資料模型之留存分析
  • 應用案例

a. 操作了某個行為的使用者在後續某一天操作了另一個行為的留存:

如“20200701點選了banner的使用者在次日點選app簽到的留存人數”,就可以用以下的sql快速求解:

Clickhouse 專欄---資料模型之留存分析

b. 操作了某個行為并且帶有某個屬性的使用者在後續的某一天操作了另一個行為的留存:

如“20200701點選了banner且來自廣東/江西/河南的使用者在次日點選app簽到的留存人數”: 

Clickhouse 專欄---資料模型之留存分析

 c. 操作了某個行為并且帶有某幾個屬性的使用者在後續的某一天操作了另一個行為的留存:

如“20200701點選了banner、來自廣東且新進管道是小米商店的使用者在次日點選app簽到的留存人數”:

Clickhouse 專欄---資料模型之留存分析

其中bitmapCardinality用來計算位圖中不重複資料個數,在大資料量下會有一定的資料誤差,bitmapAnd用來計算兩個bitmap的與操作,即傳回同時出現在兩個bitmap中使用者數量

  • 查詢速度

    clickhouse叢集現狀:12核125G記憶體機器10台。clickhouse版本:20.4.7.67。查詢的表都存放在其中一台機器上。測試了查詢在20200701操作了行為oper_name_1(使用者數量級為3000+w)的使用者在後續7天内每天操作了另一個行為oper_name_2(使用者數量級為2700+w)的留存資料(使用者重合度在1000w以上),耗時0.2秒左右

該方法的确比較靈活,不僅僅能解決留存問題,還有很多關于事件分析的需求等待我們去探索;然而它的缺點是操作複雜,且不支援對實時資料的分析

retention

通過上面的例子不難看出,騰訊的做法雖然提升了查詢的性能,但是操作過于複雜,不便于使用者了解和後期的維護;關于這些痛點易企秀數倉這邊做法是采用retention進行實作

retention function是clickhouse中進階聚合函數,較bitmap的方式實作留存分析會更加簡單、高效;文法如下:

retention(cond1, cond2, ..., cond32);
## cond 為判斷條件
## 支援最長32個參數的輸入,也就是說 至少支援一個完整自然月的留存分析查詢
           

其中滿足條件1的資料會置為1,之後的每一個表達式成立的前提都要建立在條件1成立的基礎之上,這正好符合我們對留存模型的定義

那麼我們還以上面的3個場景為例友善對比說明:

  • 20200701點選了banner的使用者在次日點選app簽到的留存人數
SELECT
    sum(r[1]) AS r1,
    sum(r[2]) AS r2,
    r2/r1
FROM
(
SELECT
    uid,
    retention(date = '20200701' and type='點選banner', date =  '20200702'  and type='點選app簽到' ) AS r
FROM action_log
WHERE date IN ('20200701', '20200702')
GROUP BY uid 
)
           
  • 20200701點選了banner且來自廣東/江西/河南的使用者在次日點選app簽到的留存人數
SELECT
    sum(r[1]) AS r1,
    sum(r[2]) AS r2,
    r2/r1
FROM
(
SELECT
    uid,
    retention(date = '20200701' and type='點選banner', date =  '20200702'  and type='點選app簽到' ) AS r
FROM action_log
WHERE date IN ('20200701', '20200702')  and province IN ('廣東', '江西', '河南')
GROUP BY uid 
)
           
  • 按照上面的方式第三個場景也能很快實作,這裡留給大家去嘗試...

不過該方式與bitmap比也有缺陷,那就是如果使用者日志表中不存儲使用者屬性資訊時,就需要與使用者屬性表進行關聯查詢,兩張大表關聯,查詢性能會相當慢。

總結

條條大路通羅馬,clickhouse不但性能上做到極緻,在資料分析實作上也提供了大量周遊的途徑。