埋点去重先定键

很多技术问题看起来是某个 API 用错了,实际更像一次边界没有提前说清的连锁反应。一次活动复盘里点击率突然翻倍,最后发现按钮曝光在页面返回、弹窗重开和接口重试时都被重复记了一次,这种情况并不稀奇:功能表面能跑,真正进入复杂路径后,隐藏假设才开始一个个冒出来。
这篇文章想讨论的不是把埋点去重键讲成一套万能口诀,而是把它放回真实工作里看:哪些规则需要提前定,哪些复杂度可以延后,哪些地方一旦偷懒就会变成排查成本。我的判断是,先把边界收住,再谈抽象、性能或体验,通常更稳。
先定义一次行为是什么
在一次活动复盘里点击率突然翻倍,最后发现按钮曝光在页面返回、弹窗重开和接口重试时都被重复记了一次这个场景里,曝光、点击、提交和支付不是同一种事件,它们的一次行为边界也不同。这不是写法洁癖,而是决定问题发生时团队能不能快速定位责任边界。埋点去重键如果没有被提前说清,后面的代码、测试和排查都会各自按自己的理解推进。
从机制上看,事件 ID、用户 ID、会话、页面实例、业务对象和上报重试不是孤立存在的。它们会在一次真实请求、一次页面切换或一次批处理任务里互相影响。理解这一层之后,就能看出为什么去重太松会放大数据,去重太严会误删真实行为。
落地时建议先做一件小事:为每个关键事件写一句业务定义。这个动作看起来慢,却能把隐藏分歧提前暴露出来。很多线上问题不是因为团队不会写代码,而是因为大家默认的边界根本不是同一个。
这里最容易踩的坑是:没有定义时,去重键只能凭感觉拼。它通常不会在第一天爆炸,而是在数据量变大、用户路径变复杂、或者某个下游服务变慢时突然出现。到那时再补规则,成本会高很多。
判断这部分做得好不好,不要只看功能是否跑通,而要看同一用户一次行为只算一次,重复上报能被识别,真实多次行为不会被吞。如果答案仍然含糊,说明设计还停留在感觉层面,需要继续把条件、异常和责任写具体。
在“先定义一次行为是什么”这一段里,我更愿意把复杂度摊开放到日志、状态和验收规则里,而不是塞进默认行为。这样做不一定显得聪明,但后续排查会更稳:谁触发、谁处理、失败后谁接手,都能在材料里找到依据。
图里只保留了和埋点去重键直接相关的路径,目的不是画全系统,而是帮助你判断问题应该从哪一层开始拆。
页面实例比页面名称更有用
在一次活动复盘里点击率突然翻倍,最后发现按钮曝光在页面返回、弹窗重开和接口重试时都被重复记了一次这个场景里,用户返回同一路由不一定是同一次浏览,弹窗重开也可能是新上下文。这不是写法洁癖,而是决定问题发生时团队能不能快速定位责任边界。埋点去重键如果没有被提前说清,后面的代码、测试和排查都会各自按自己的理解推进。
放到“页面实例比页面名称更有用”这个小节里看,相关机制并不是背景知识,事件 ID、用户 ID、会话、页面实例、业务对象和上报重试不是孤立存在的。它们会在一次真实请求、一次页面切换或一次批处理任务里互相影响。理解这一层之后,就能看出为什么去重太松会放大数据,去重太严会误删真实行为。
落地时建议先做一件小事:生成 page_instance_id 并贯穿子组件。这个动作看起来慢,却能把隐藏分歧提前暴露出来。很多线上问题不是因为团队不会写代码,而是因为大家默认的边界根本不是同一个。
这里最容易踩的坑是:只用页面名称去重,会把不同浏览合并掉。它通常不会在第一天爆炸,而是在数据量变大、用户路径变复杂、或者某个下游服务变慢时突然出现。到那时再补规则,成本会高很多。
在“页面实例比页面名称更有用”这里,验收不该只看一句通过,不要只看功能是否跑通,而要看同一用户一次行为只算一次,重复上报能被识别,真实多次行为不会被吞。如果答案仍然含糊,说明设计还停留在感觉层面,需要继续把条件、异常和责任写具体。
在“页面实例比页面名称更有用”这一段里,我更愿意把复杂度摊开放到日志、状态和验收规则里,而不是塞进默认行为。这样做不一定显得聪明,但后续排查会更稳:谁触发、谁处理、失败后谁接手,都能在材料里找到依据。
针对“页面实例比页面名称更有用”,可以把检查动作落成三项:
先写清本场景里的关键对象:埋点去重键。
再标出会影响它的机制:事件 ID、用户 ID、会话、页面实例、业务对象和上报重试。
最后补上失败时的判断标准:同一用户一次行为只算一次,重复上报能被识别,真实多次行为不会被吞。
重试上报要能被识别
在一次活动复盘里点击率突然翻倍,最后发现按钮曝光在页面返回、弹窗重开和接口重试时都被重复记了一次这个场景里,网络失败后的补发是同一事件,不应该变成新行为。这不是写法洁癖,而是决定问题发生时团队能不能快速定位责任边界。埋点去重键如果没有被提前说清,后面的代码、测试和排查都会各自按自己的理解推进。
放到“重试上报要能被识别”这个小节里看,相关机制并不是背景知识,事件 ID、用户 ID、会话、页面实例、业务对象和上报重试不是孤立存在的。它们会在一次真实请求、一次页面切换或一次批处理任务里互相影响。理解这一层之后,就能看出为什么去重太松会放大数据,去重太严会误删真实行为。
落地时建议先做一件小事:客户端生成 event_id,服务端按 event_id 幂等。这个动作看起来慢,却能把隐藏分歧提前暴露出来。很多线上问题不是因为团队不会写代码,而是因为大家默认的边界根本不是同一个。
这里最容易踩的坑是:服务端只看到达时间,会把补发误当新事件。它通常不会在第一天爆炸,而是在数据量变大、用户路径变复杂、或者某个下游服务变慢时突然出现。到那时再补规则,成本会高很多。
在“重试上报要能被识别”这里,验收不该只看一句通过,不要只看功能是否跑通,而要看同一用户一次行为只算一次,重复上报能被识别,真实多次行为不会被吞。如果答案仍然含糊,说明设计还停留在感觉层面,需要继续把条件、异常和责任写具体。
在“重试上报要能被识别”这一段里,我更愿意把复杂度摊开放到日志、状态和验收规则里,而不是塞进默认行为。这样做不一定显得聪明,但后续排查会更稳:谁触发、谁处理、失败后谁接手,都能在材料里找到依据。
换到“重试上报要能被识别”这一步,图里只保留了和埋点去重键直接相关的路径,目的不是画全系统,而是帮助你判断问题应该从哪一层开始拆。
下面这段代码只表达思路,重点不在复制,而在看清边界放在哪里:

  1. event_key = f"{user_id}:{page_instance_id}:{event_name}:{object_id}:{action_seq}"

text
去重窗口要贴合业务节奏
在一次活动复盘里点击率突然翻倍,最后发现按钮曝光在页面返回、弹窗重开和接口重试时都被重复记了一次这个场景里,按钮连点可能要合并,用户隔几分钟再次点击可能是真实意图。这不是写法洁癖,而是决定问题发生时团队能不能快速定位责任边界。埋点去重键如果没有被提前说清,后面的代码、测试和排查都会各自按自己的理解推进。
放到“去重窗口要贴合业务节奏”这个小节里看,相关机制并不是背景知识,事件 ID、用户 ID、会话、页面实例、业务对象和上报重试不是孤立存在的。它们会在一次真实请求、一次页面切换或一次批处理任务里互相影响。理解这一层之后,就能看出为什么去重太松会放大数据,去重太严会误删真实行为。
落地时建议先做一件小事:为不同事件设置不同窗口。这个动作看起来慢,却能把隐藏分歧提前暴露出来。很多线上问题不是因为团队不会写代码,而是因为大家默认的边界根本不是同一个。
这里最容易踩的坑是:统一五分钟去重,看似省事,实际会伤害分析。它通常不会在第一天爆炸,而是在数据量变大、用户路径变复杂、或者某个下游服务变慢时突然出现。到那时再补规则,成本会高很多。
在“去重窗口要贴合业务节奏”这里,验收不该只看一句通过,不要只看功能是否跑通,而要看同一用户一次行为只算一次,重复上报能被识别,真实多次行为不会被吞。如果答案仍然含糊,说明设计还停留在感觉层面,需要继续把条件、异常和责任写具体。
在“去重窗口要贴合业务节奏”这一段里,我更愿意把复杂度摊开放到日志、状态和验收规则里,而不是塞进默认行为。这样做不一定显得聪明,但后续排查会更稳:谁触发、谁处理、失败后谁接手,都能在材料里找到依据。
针对“去重窗口要贴合业务节奏”,可以把检查动作落成三项:
先写清本场景里的关键对象:埋点去重键。
在“去重窗口要贴合业务节奏”里标出会影响它的机制:事件 ID、用户 ID、会话、页面实例、业务对象和上报重试。
为“去重窗口要贴合业务节奏”补上失败时的判断标准:同一用户一次行为只算一次,重复上报能被识别,真实多次行为不会被吞。
换到“去重窗口要贴合业务节奏”这一步,图里只保留了和埋点去重键直接相关的路径,目的不是画全系统,而是帮助你判断问题应该从哪一层开始拆。
验收要用反例数据
在一次活动复盘里点击率突然翻倍,最后发现按钮曝光在页面返回、弹窗重开和接口重试时都被重复记了一次这个场景里,准备重复曝光、真实二次点击、断网补发、跨端操作四组样本。这不是写法洁癖,而是决定问题发生时团队能不能快速定位责任边界。埋点去重键如果没有被提前说清,后面的代码、测试和排查都会各自按自己的理解推进。
放到“验收要用反例数据”这个小节里看,相关机制并不是背景知识,事件 ID、用户 ID、会话、页面实例、业务对象和上报重试不是孤立存在的。它们会在一次真实请求、一次页面切换或一次批处理任务里互相影响。理解这一层之后,就能看出为什么去重太松会放大数据,去重太严会误删真实行为。
落地时建议先做一件小事:让数据同学和业务同学一起确认口径。这个动作看起来慢,却能把隐藏分歧提前暴露出来。很多线上问题不是因为团队不会写代码,而是因为大家默认的边界根本不是同一个。
这里最容易踩的坑是:只有正向样本的埋点验收,很难发现去重过度。它通常不会在第一天爆炸,而是在数据量变大、用户路径变复杂、或者某个下游服务变慢时突然出现。到那时再补规则,成本会高很多。
在“验收要用反例数据”这里,验收不该只看一句通过,不要只看功能是否跑通,而要看同一用户一次行为只算一次,重复上报能被识别,真实多次行为不会被吞。如果答案仍然含糊,说明设计还停留在感觉层面,需要继续把条件、异常和责任写具体。
在“验收要用反例数据”这一段里,我更愿意把复杂度摊开放到日志、状态和验收规则里,而不是塞进默认行为。这样做不一定显得聪明,但后续排查会更稳:谁触发、谁处理、失败后谁接手,都能在材料里找到依据。
收尾时看这三个信号
第一,看问题能不能被命名。比如这篇里的核心不是泛泛的“优化一下”,而是埋点去重键有没有清楚边界。能命名的问题,才容易进入评审、测试和复盘。
第二,看失败能不能被复现。围绕同一用户一次行为只算一次,重复上报能被识别,真实多次行为不会被吞设计一组小样本,比等线上偶发问题更可靠。样本不需要复杂,但要覆盖正常、异常、边界和恢复。
第三,看团队能不能做出一致选择。去重太松会放大数据,去重太严会误删真实行为,这类取舍没有绝对答案,但必须有理由、有记录、有回滚空间。否则今天靠经验放过的点,明天就会变成另一个人看不懂的坑。
真正有价值的工程文章,不是把每个概念都讲满,而是帮读者在下次遇到类似场景时更早地停一下:这件事的边界定了吗,失败路径想过了吗,验收标准能说清吗。只要这三个问题能回答,很多复杂度就已经少了一半。