伴魚事件分析平臺設計

背景

在伴魚,服務器每天收集的用戶行爲日誌達到上億條,我們希望能夠充分利用這些日誌,瞭解用戶行爲模式,回答以下問題:

爲了回答這些問題,事件分析平臺應運而生。本文將首先介紹平臺的功能,隨後討論平臺在架構上的一些思考。

功能

總的來說,爲了回答各種商業分析問題,事件分析平臺支持基於事件的指標統計、屬性分組、條件篩選等功能的查詢。其中,事件指用戶行爲,例如登錄、瀏覽伴魚繪本、購買付費繪本等。更具體一些,事件分析平臺支持三類分析:「事件分析」,「漏斗分析」,和「留存分析」。

事件分析

事件分析是指,用戶指定一系列條件,查詢目的指標,用於回答一個具體的分析問題。這些條件包括:

讓我們舉個具體的例子。我們希望回答「最近一週,在北京地區,不同年齡段的用戶在下單一對一課程時,下單金額的平均數對比」這個問題。這個問題可以很直觀地拆解爲下圖所示的事件分析,其中:

圖注:事件分析創建流程

圖注:事件分析界面

漏斗分析

漏斗分析用於分析多步驟過程中,每一步的轉化與流失情況。

例如,伴魚繪本用戶的完整購買流程可能包含以下步驟:登錄 app -> 瀏覽繪本 -> 購買付費繪本。我們可以將這個流程設置爲一個漏斗,分析整體以及每一步轉化情況。

此外,漏斗分析還需要定義「窗口期」,整個流程必須發生在窗口期內,纔算一次成功轉化。和事件分析類似,漏斗分析也支持選擇維度分組和時間範圍。

圖注:漏斗分析創建流程

圖注:漏斗分析界面

留存分析

在留存分析中,用戶定義初始事件和後續事件,並計算在發生初始事件後的第 N 天,發生後續事件的比率。這個比率能很好地衡量伴魚用戶的粘性高低。

在下圖的例子中,我們希望瞭解伴魚繪本 app 是否足夠吸引用戶,因此我們設置初始事件爲登錄 app,後續事件爲瀏覽繪本,留存週期爲 7 天,進行留存分析。

圖注:留存分析創建流程

圖注:留存分析界面

架構

在架構上,事件分析平臺分爲兩個模塊,如下圖所示:

圖注:總架構圖

不難看出,ClickHouse 是構成事件分析平臺的核心組件。我們爲了確保平臺的性能,圍繞 ClickHouse 的使用進行了細緻的調研,回答了以下三個問題:

如何使用 ClickHouse 存儲事件數據?

事件分析平臺的數據來源有兩大類:來源於埋點日誌的用戶行爲數據,和來源於「用戶畫像平臺」的用戶屬性數據。本文只介紹埋點日誌數據的存儲,對「用戶畫像平臺」感興趣的同學,可以期待一下我們後續的技術文章。

在進行埋點日誌的存儲選型前,我們首先明確了幾個核心需求:

ClickHouse 在海量數據存儲場景被廣泛使用,高效支持各類聚合查詢,配套有成熟和活躍的社區,促使我們最終選擇 ClickHouse 作爲存儲引擎。

根據我們對真實埋點數據的測試,億級數據的簡單查詢,例如 PV 和 UV,都能在 1 秒內返回結果;對於留存分析、漏斗分析這類的複雜查詢,可以在 10 秒內返回結果。

「存在哪」的問題解決後,接下來回答「怎麼存」的問題。ClickHouse 的列式存儲結構非常適合存儲大寬表,以支持高效查詢。但是,在事件分析平臺這個場景下,我們還需要支持「自定義屬性」的存儲,這時「大寬表」的存儲方式就不盡理想。

所謂「自定義屬性」,即埋點日誌中一些事件所獨有的屬性,例如:「下單一對一課程」這一事件在上報時,會帶上「訂單金額」這個很多其它事件所沒有的屬性。如果爲了支持「下單一對一課程」這個事件的存儲,就需要改變 ClickHouse 的表結構,新增一列,這將使得表結構的維護成本極高,因爲每個新事件都可能附帶多個「自定義屬性」。

爲了解決這個問題,我們將頻繁變動的自定義屬性統一存儲在一個 Map 中,將基本不變的公共屬性存爲列,使之兼具大寬表方案的高效性,和 Map 方案的靈活性。

如何高效寫入 ClickHouse?

在設計 ClickHouse 的部署方案時,我們採用了業界常用的讀寫分離模式:寫本地表,讀分佈式表。在寫入側,分爲 3 個分片,每個分片都有雙重備份。

由於事件分析的絕大多數查詢,都是以用戶爲單位,爲了提高查詢效率,我們在寫入時,將數據按照 user_id 均勻分片,寫入到不同的本地表中。如下圖所示:

圖注:將埋點數據寫入到 ClickHouse

之所以不寫分佈式表,是因爲我們使用大量數據對分佈式表進行寫入測試時,遇到過幾個問題:

  1. Too many parts error:分佈式表所在節點接收到數據後,需要按照 sharding_key 將數據拆分爲多個 parts,再轉發到其它節點,導致短期內 parts 過多,並且增加了 merge 的壓力;

  2. 寫放大:分佈式表所在節點,如果在短時間內被寫入大量數據,會產生大量臨時數據,導致寫放大。

如何高效查詢 ClickHouse?

我們可以使用 ClickHouse 的內置函數,輕鬆實現事件分析平臺所需要提供的事件分析、漏斗分析和留存分析三個功能。

事件分析可以用最樸素的 SQL 語句實現。例如,最近一週,北京地區的,發生過繪本瀏覽行爲的用戶,按照年齡段的分佈,可以表述爲:

SELECT
    count(1) as cnt,
    toDate(toStartOfDay(toDateTime(event_ms))) as date,
    age
FROM event_analytics
WHERE
  event = "view_picture_book_home_page" AND
  city = "beijing" AND
  event_ms >= 1613923200000 AND event_ms <= 1614528000000
GROUP BY (date, age);

留存分析使用 ClickHouse 提供的 retention 函數。例如,註冊伴魚繪本後,計算瀏覽繪本的次日留存、7 日留存可以表述爲:

SELECT
    sum(ret[1]) AS original,
    sum(ret[2]) AS next_day_ret,
    sum(ret[3]) AS seven_day_ret
FROM
(SELECT
  user_id,
  retention(
      event = "register_picture_book" AND toDate(event_ms) = toDate('2021-03-01'),
      event = "view_picture_book" AND toDate(event_ms) = toDate('2021-03-02'),
      event = "view_picture_book" AND toDate(event_ms) = toDate('2021-03-08')
      ) as ret
FROM event_analytics
WHERE  
    event_ms >= 1614528000000 AND event_ms <= 1615132800000
GROUP BY user_id);

漏斗分析使用 ClickHouse 提供的 windowFunnel 函數。例如,在 瀏覽繪本 -> 購買繪本,窗口期爲 2 天的這個轉化路徑上,轉化率的計算可以被表達爲:

SELECT
array( sumIf(count, level >= 1), sumIf(count, level >= 2) ) AS funnel_uv,
FROM (
SELECT
level,
count() AS count
FROM (
SELECT
                uid,
                windowFunnel(172800000)(
                    event_ms, event = "view_picture_book" AND event_ms >= 1613923200000 AND event_ms <= 1614009600000, event = "buy_picture_book") AS level
FROM
                event_analytics
WHERE
                event_ms >= 1613923200000 AND event_ms <= 1614182400000
GROUP BY uid
        )
GROUP BY level
)
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/eKm6ub3hrNnB9v6xg2196w