ClickHouse中如何实现漏斗分析

问题描述

我想根据存储在 ClickHouse 中的埋点数据进行漏斗分析。让我们为漏斗分析定义一些元素:

  1. 一系列事件:A (event_id = 1) -> B (event_id = 2) -> C (event_id = 3)

  2. 时间段:0 (event_ms) ~ 500 (event_ms)

  3. 时间窗口:100 (event_ms)

我想知道,对于每个用户,是否有一个事件系列(A->B->C)发生在时间段内,并且A和C之间的间隔在时间窗口内。

这是我的测试数据集:

CREATE TABLE test_dataset
(
    `event_id` UInt64,`event_ms` UInt64,`uid` UInt64 // user_id
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMMDD(toDate(event_ms))
ORDER BY (event_id,event_ms,uid)
SETTINGS index_granularity = 8192;

INSERT INTO TABLE test_dataset VALUES 
      (1,100,123),(1,120,130,150,345),180,(2,200,234),140,210,300,(3,250,290,270,345);

我使用 join 查找所有符合条件的事件系列:

SELECT
    t1.event_ms,t2.event_ms,t3.event_ms,t4.event_ms,t1.uid,t2.uid,t3.uid,t4.uid
FROM
(SELECT 
    uid,event_ms 
FROM funnel_join_test_1
WHERE
    event_id = 1 AND event_ms >= 0 AND event_ms <= 500) as t1
ASOF left join
(SELECT 
    uid,event_ms 
FROM funnel_join_test_1
WHERE
    event_id = 2 AND event_ms >= 0 AND event_ms <= 500) as t2
ON t1.uid = t2.uid AND t1.event_ms  < t2.event_ms
ASOF left join
(SELECT 
    uid,event_ms 
FROM funnel_join_test_1
WHERE
    event_id = 3 AND event_ms >= 0 and event_ms <= 500) as t3
ON t2.uid = t3.uid and t2.event_ms < t3.event_ms
ASOF left join
(SELECT 
    uid,event_ms 
FROM funnel_join_test_1
WHERE
    event_id = 3 AND event_ms >= 0 and event_ms <= 500) as t4
ON t3.uid = t4.uid and t4.event_ms < t1.event_ms + 100
WHERE t4.event_ms > 0;

以下是所有合格的活动系列:

┌─t1.event_ms─┬─t2.event_ms─┬─t3.event_ms─┬─t4.event_ms─┬─t1.uid─┬─t2.uid─┬─t3.uid─┬─t4.uid─┐
│         180 │         210 │         270 │         270 │    345 │    345 │    345 │    345 │
└─────────────┴─────────────┴─────────────┴─────────────┴────────┴────────┴────────┴────────┘
┌─t1.event_ms─┬─t2.event_ms─┬─t3.event_ms─┬─t4.event_ms─┬─t1.uid─┬─t2.uid─┬─t3.uid─┬─t4.uid─┐
│         120 │         150 │         180 │         180 │    123 │    123 │    123 │    123 │
└─────────────┴─────────────┴─────────────┴─────────────┴────────┴────────┴────────┴────────┘
┌─t1.event_ms─┬─t2.event_ms─┬─t3.event_ms─┬─t4.event_ms─┬─t1.uid─┬─t2.uid─┬─t3.uid─┬─t4.uid─┐
│         130 │         150 │         180 │         180 │    123 │    123 │    123 │    123 │
└─────────────┴─────────────┴─────────────┴─────────────┴────────┴────────┴────────┴────────┘
┌─t1.event_ms─┬─t2.event_ms─┬─t3.event_ms─┬─t4.event_ms─┬─t1.uid─┬─t2.uid─┬─t3.uid─┬─t4.uid─┐
│         100 │         150 │         180 │         180 │    123 │    123 │    123 │    123 │
└─────────────┴─────────────┴─────────────┴─────────────┴────────┴────────┴────────┴────────┘

然后我知道用户 123 和 345 在时间段内有这样的事件系列。在 ClickHouse 中使用 join 非常慢,有没有其他方法可以解决这个问题?

顺便说一句,我不需要知道所有符合条件的系列,我只想知道每个用户是否有一个这样的事件系列。

解决方法

有函数 windowFunnel 在滑动窗口中搜索事件链。

SELECT
    uid,windowFunnel(100)(event_ms,event_id = 1,event_id = 2,event_id = 3) AS chain_len
FROM test_dataset
WHERE (event_ms > 0) AND (event_ms < 500)
GROUP BY uid;

结果:

┌─uid─┬─chain_len─┐
│ 234 │         0 │
│ 345 │         3 │
│ 123 │         3 │
└─────┴───────────┘

它返回匹配的链长度,因此对于用户 345123,我们有 3 表示匹配整个链。

如果我们将 window 减少到 10,它只会找到链的开头,并且由于条件 timestamp of event 2 <= timestamp of event 1 + window 不成立而不会匹配其他事件。

SELECT
    uid,windowFunnel(10)(event_ms,event_id = 3) AS chain_len
FROM test_dataset
WHERE (event_ms > 0) AND (event_ms < 500)
GROUP BY uid

结果:

┌─uid─┬─chain_len─┐
│ 234 │         0 │
│ 345 │         1 │
│ 123 │         1 │
└─────┴───────────┘

因此,要检查用户是否存在这样的链,您可以检查 windowFunnel 是否匹配了适当数量的事件。

时间间隔限制(时间周期:0 (event_ms) ~ 500 (event_ms)),在WHERE子句中简单处理。

在周期外添加更多事件:

INSERT INTO TABLE test_dataset VALUES (1,600,234),(2,601,(3,602,234);

然后检查:

SELECT
    uid,event_id = 3) AS chain_len
FROM test_dataset
WHERE (event_ms > 0) AND (event_ms < 500)
GROUP BY uid

结果:

┌─uid─┬─chain_len─┐
│ 234 │         0 │
│ 345 │         3 │
│ 123 │         3 │
└─────┴───────────┘

没有WHERE

SELECT
    uid,event_id = 3) AS chain_len
FROM test_dataset
GROUP BY uid

结果:

┌─uid─┬─chain_len─┐
│ 234 │         3 │
│ 345 │         3 │
│ 123 │         3 │
└─────┴───────────┘