ClickHouse 埋点事件表:先验证时间与重复,再谈漏斗看板

埋点请求返回 200,并不等于事件已经以正确语义进入 ClickHouse。一个可复核的接收链路,至少要把事件时间、接收时间、身份和重复定义分开。下面用一张最小示例表和几条 SQL 演示验收方法;它是独立的测试表,不是任何产品的生产建表模板。

为什么保留两个时间?

event_time 是行为发生时间,received_at 是接收端看到请求的时间。移动端离线缓存、批量上传或重试会使两者相差很远。业务报表通常需要按事件时间归属日期;排查延迟则要看接收时间。只保存一个模糊的 timestamp,会让跨天数据难以解释。

一张可直接执行的测试表

以下 SQL 使用 ClickHouse 的 MergeTree、DateTime64 和 LowCardinality 类型。可在独立测试库执行;若要重复演练,请更换表名或先清理自己的测试表,不要对生产表操作。

sql 复制代码
CREATE TABLE IF NOT EXISTS event_ingest_demo
(
    project_id  String,
    event_name  LowCardinality(String),
    distinct_id String,
    event_time  DateTime64(3, 'UTC'),
    received_at DateTime64(3, 'UTC'),
    event_id    String,
    properties  String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (project_id, event_name, toDate(event_time),
          distinct_id, event_time, event_id);

INSERT INTO event_ingest_demo VALUES
('demo', 'signup', 'user-1',
 toDateTime64('2026-09-29 10:00:00', 3, 'UTC'),
 toDateTime64('2026-09-29 10:00:02', 3, 'UTC'),
 'evt-001', '{"plan":"free"}'),
('demo', 'signup', 'user-1',
 toDateTime64('2026-09-29 10:00:00', 3, 'UTC'),
 toDateTime64('2026-09-29 10:00:04', 3, 'UTC'),
 'evt-001', '{"plan":"free"}'),
('demo', 'purchase', 'user-1',
 toDateTime64('2026-09-29 10:03:00', 3, 'UTC'),
 toDateTime64('2026-09-29 10:03:01', 3, 'UTC'),
 'evt-002', '{"amount":39}');

第一、二行故意使用相同的 event_id,模拟同一业务事件被传输重试。第三行是另一个事件。当前 MergeTree 表不会自动删除前两行。

先看原始行,而不是直接打开看板:

sql 复制代码
SELECT event_name, distinct_id, event_id,
       event_time, received_at, properties
FROM event_ingest_demo
WHERE project_id = 'demo'
ORDER BY event_time, received_at;

再检查是否有共享事件 ID 的记录:

sql 复制代码
SELECT project_id, event_id, count() AS copies
FROM event_ingest_demo
WHERE project_id = 'demo' AND event_id != ''
GROUP BY project_id, event_id
HAVING copies > 1
ORDER BY copies DESC;

在这组样本中,查询应返回 evt-001、copies = 2。如果客户端协议没有稳定事件 ID,不能简单用"同一用户 + 同一事件名 + 同一秒"代替:用户可能真的连续操作两次。需要先定义去重键,才能谈去重率。

为什么不直接换成 ReplacingMergeTree?

ClickHouse 官方文档说明,ReplacingMergeTree 根据 ORDER BY 排序键在后台合并时移除重复行;合并发生时间不确定,因此不保证查询时已经没有重复。FINAL 可用于查询时处理,但不能把它当作无需评估成本的默认开关。

更重要的是:如果排序键包含 received_at,一次重试就可能产生不同键,根本不会被这套规则视为重复。如果排序键过短,又可能误合并真实事件。因此先用样本确认事件 ID、版本字段和查询口径,再决定在接收层、写入层还是查询层处理重复。

从测试表走向真实链路

真实事件链路还要验证空身份、未来时间、迟到事件、属性类型变化、写入失败和重试后的最终行数。Superset 图表适合展示查询结果,但不能替代原始行对账与接收服务监控。

SensorFlow 公开仓库的架构是兼容的 Sensors Data SDK 上报 → Go 接收 → ClickHouse → Apache Superset。其无许可证 Demo 可以先看样本事件;真实 SDK 数据接收需要许可证,并且应按实际 SDK 版本和 payload 验证。本文的 event_ingest_demo 只是通用 ClickHouse 验收练习,不代表 SensorFlow 的现有生产表结构或已完成全模式兼容测试。

建表时最终的分区、排序键、TTL、副本与批量写入策略,应按自己的查询模式、数据量和故障要求测试,而不是直接复制演示 SQL。MergeTree 官方参考可作为起点。

相关推荐
feely02081 小时前
《我写了 31 个静态扫描插件,踩了 7 个「假绿」》
后端
橙子家1 小时前
参数校验类异常(Argument Exception)【C# Exception 详解系列一】
后端
掘金挖土2 小时前
前端手摸手跑路之 AI 应用开发(九)
前端·后端
全栈弄潮儿2 小时前
Python实战第8期:函数
后端·python·编程语言
码事漫谈3 小时前
你让 AI “尽力做”,它反而可能做不好
后端
孟健3 小时前
LLM API 安全防线:从 OpenCode 盗刷事件到 LiteLLM 多模型容灾实操
人工智能·后端·架构
程序员cxuan3 小时前
我用 WorkBuddy + ima 把公众号文章做成了知识库。
人工智能·后端·程序员
程序员cxuan4 小时前
玩了一天 REA,我号快没了
人工智能·后端·程序员
卷无止境4 小时前
暗流与破局:AI时代的代码安全审查工具全景指南
后端·python