事件名是写给三个月后的自己的一封信,不是写给今天跑通埋点的那个下午。
一、三个名字,一段拼不起来的数据
周岚做协作笔记工具「页边」做了两年。三个月前她第一次埋点,注册事件叫 signup;上个月重构登录流程,顺手把它改成了 user_registered;这周她觉得动词应该统一放在后面,又想改成 register。每次改完她都重新验证过一遍,埋点确实在发,数据确实在进。
问题出在她想看「注册到激活」的转化率那天。她把三个月的数据拉到一起,发现 8 月有 signup,9 月前半段有 signup、后半段有 user_registered,10 月三种都有一些——旧版本客户端还在用户手机上没有更新。三段各自能算出一个转化率,拼起来却比后台账号新增少了 40%。
以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。
她最后是用后台的账号创建时间手工补的缺口。补完之后她意识到一件事:那 40% 不是数据丢了,是数据还在、名字变了,而她没有一张表能说明「这三个名字指的是同一件事」。
事件名是长期资产。 一个事件一旦上线,就会同时存在于客户端、服务端、报表、邮件模板和你三个月后写的复盘里。改名的成本不由改的那一天决定,而由引用过它的每一处决定,而你不记得有多少处。
字典是给三个月后的自己写的。 今天的你知道 register 和 user_registered 的区别,因为是你改的。三个月后的你只会看到两个陌生的名字和两段对不上的数字,然后花一个下午去猜。
属性比事件名更容易失控。 名字改一次是一个决定,属性的取值却会随着每次发布悄悄长出新值:plan 从 free/pro 变成 free/pro/team,source 从四个渠道变成十一个。没人改过字典,但字典已经不对了。
没有变更表,改名就是删历史。 改名本身不是错,错在没有留下「旧名到新名」的映射和日期。有了这张表,90 天内的数据仍然可以合并;没有它,改名的那一刻,之前的历史就变成了另一个产品的历史。
二、二十四个事件,五个组
字典的第一个决定是「记多少个」。记太少,回答不了「哪步卡住」;记太多,一个人维护不住,半年后没人知道哪个还在发。这一组基线取 24 个,理由不是整齐,而是它刚好覆盖从访问到付费的完整链路,并且多出来的每一个都能说出归属。
| 组 | 个数 | 事件示例 | 主要归属 |
|---|---|---|---|
| 访问 | 4 | page_viewed、session_started、cta_clicked、search_performed | 前端 |
| 账号 | 5 | signup_submitted、email_verified、login_succeeded、profile_updated、password_reset | 双端 |
| 核心动作 | 7 | project_created、template_applied、document_exported、item_published、comment_posted、integration_connected、invite_sent | 双端 |
| 计费 | 5 | plan_selected、checkout_started、subscription_activated、payment_failed、subscription_canceled | 服务端 |
| 异常 | 3 | api_error、client_crash、quota_exceeded | 双端 |
分组的依据是用户生命周期,不是页面。按页面分组(首页、编辑器、设置页)看起来很直观,但页面会改版、会合并、会消失,而「访问—账号—核心动作—计费—异常」这五段在两年里基本不变。分组稳定的好处是:新增一个事件时,你能立刻判断它属于哪一段,而不是先想它出现在哪个页面。
「主要归属」这一列写的是谁负责发这个事件,不是谁负责看。前端发访问与交互,服务端发计费与权限相关的动作。双端的意思是两边都可能发,此时必须约定一个为准,通常是服务端,因为客户端会因为离线、拦截、旧版本而漏发。
七个核心动作是这张表的重点。它们对应「用户做成了什么事」,而不是「用户点了什么」。document_exported 是做成了一件事,export_button_clicked 只是试了一下。前者能算转化,后者只能算兴趣,而兴趣几乎从来不改变决定。
三、命名与属性:object_action 与六个必填
命名只留一条规则:object_action,全小写,下划线分隔,动词用过去式。project_created、plan_selected、payment_failed 都符合;createProject、ProjectCreated、新建项目 都不符合。
过去式不是语法洁癖,它对应一个事实:事件记录的是已经发生的事。用现在时或动词原形,读的人会下意识把它当成一个可以触发的动作,进而在代码里写出「调用 create_project 事件」这种把埋点当接口的用法。命名一旦暗示「可以调用」,埋点就会开始被业务逻辑复用,最后没人说得清它到底代表什么。
| 属性 | 类别 | 取值示例 | 缺失时的后果 |
|---|---|---|---|
user_id | who | u_8f21c | 无法按人算留存,同一人两次访问被算成两个人 |
account_id | who | acc_1042 | 多人共用账号时分不清操作者,席位与权限类指标全错 |
anon_id | who | anon_7d3e | 注册前的行为断链,激活漏斗看不到第一次访问 |
object_id | what | doc_5521 | 无法回答「这一份文档被导出过几次」,只能算总次数 |
plan | what | free / pro / team | 收入按套餐拆分只能回账单系统手工对,且对不上 |
client_ts | when | 2026-10-26T09:10:00+08:00 | 离线补传与时钟漂移无法定位,事件顺序会乱 |
六个属性回答三个问题:谁、对什么、什么时候。三个 who 不是重复:user_id 是操作者,account_id 是付费与权限的主体,anon_id 是注册前的匿名身份。少了任何一个,都会有一类问题永远答不了,而且是事后补不回来的那种。
client_ts 之外,服务端还要补两个:server_ts 与 event_id。前者是服务端收到的时刻,用来发现客户端时钟偏差和离线补传;后者是幂等键,用来在重试与重复上报时去重。只有 client_ts 时,一台系统时间错了两个月的设备可以把整条漏斗的顺序搅乱,而你看不出来。
属性比事件名更容易失控,因为它有取值域。plan 一开始只有两个值,加了团队版变成三个,促销期又多了一个 pro_trial。这些变化不会经过任何评审,但每一个都会让三个月前的报表多出一个「未知」分类。所以字典里每个属性除了名字,还要写允许的取值和「新增取值要走哪一步」。
四、复算:144 个字段与 8 小时
字典的成本可以算,而且算完会发现它比想象中便宜。下面按「每个事件 10 分钟」估算,这个时间包括写下名字、归属、六个属性、触发时机和一句反例。
字段规模:
24 个事件 × 6 个属性 = 144 个字段
服务端另补 server_ts、event_id:24 × 2 = 48 个字段
合计 192 个字段,其中必填 144 个
首次建字典:
24 个事件 × 10 分钟 = 240 分钟 = 4 小时
年度维护:
每月新增 2 个事件 × 10 分钟 = 20 分钟/月
20 分钟 × 12 = 240 分钟 = 4 小时/年
第一年总成本 = 4 + 4 = 8 小时
报空率:
24 个事件里实际有量的 18 个
6 ÷ 24 = 25%
即每四个事件里有一个从未产生过数据
改名成本:
3 处改名 → 需要 3 张映射表,才能回看 90 天
| 类型 | 事件 | 日期 | 映射 |
|---|---|---|---|
| 新增 | document_exported | 2026-08-14 | 无历史,从零开始计数 |
| 改名 | user_registered | 2026-09-02 | signup → user_registered,向前兼容 90 天 |
| 废弃 | signup | 2026-12-01 | 归并到 user_registered,客户端停止写入 |
8 小时里,大部分不是打字的时间。 144 个字段如果只是填名字,两个小时就够。真正花时间的是每个事件的「触发时机」和「一句反例」:什么时候算创建成功,草稿算不算,导入算不算。这些判断每写一个都要回到产品里确认一次,而它们恰恰是三个月后最需要的部分。
25% 的报空率不是浪费,是信号。 六个事件没有量,只有两种可能:功能没上线,或者埋点坏了。两种都需要处理,但处理方式完全相反。字典里给每个事件标一个「预期有量」的日期,就能把这两种情况分开——过了日期仍然是零,就是坏了。
改名三次就是三张映射表。 每张表要写清旧名、新名、生效日期和兼容截止日。三张表意味着任何一次跨 90 天的对比,都要先做三次合并再算比例。这不是不能做,而是每做一次都有一次算错的机会。
变更表只记三件事:新增、改名、废弃。 三列足够:类型、日期、映射。新增写「无历史」,改名写「旧名 → 新名 + 兼容期」,废弃写「归并到哪个事件 + 停止写入的日期」。不要在这张表里写「优化了属性」,那属于版本记录,不属于字典变更。
字典的价值在「缺失时的后果」这一列。 只写「plan 是套餐」没有人会认真填,写上「缺失后收入无法按套餐拆分」才会有人在提测时检查。这一列是字典里唯一能改变行为的列,其余列只是描述。
10 分钟是一个偏乐观的估计。 第一次建字典时,光是决定「导入算不算 project_created」就可能花掉二十分钟。但这件事只做一次,之后每个新事件都照着同一张表填,边际成本会降到几分钟。真正的成本从来不是建表,是建表之前那些没想清楚的定义。
五、行动清单与复算验收
这八条从「最小可用字典」开始,不要求一次写满 24 个。
- 列出当前所有在发的事件名,按访问、账号、核心动作、计费、异常分五组。
- 把不符合
object_action过去式的事件列出来,能改的改,不能改的登记进变更表。 - 给每个事件补上六个必填属性的取值,缺哪个就在字典里标红。
- 为每个属性写允许取值与新增取值的流程,尤其是
plan和渠道类属性。 - 给每个事件写一句「触发时机」和一句「什么不算」,各不超过两行。
- 建变更表,先补记过去半年所有改名与废弃,日期按上线日记。
- 给六个没量的事件标上「预期有量日期」,到期仍为零就当成故障排查。
- 把字典放在仓库里,与代码同一次提交更新,不放文档工具。
验收标准:报表上任何一个数字,都能追到具体事件的哪个属性;追不到的数字,不许写进周报。
使用边界与实操验收
本文不覆盖事件怎么发出去。 客户端与服务端各发哪些、丢失率怎么测、上线前做哪三处校验,是下一篇的内容。本篇只解决「发什么、叫什么、带哪些属性」,假设你已经能发出事件。
字典不是越全越好。 24 个事件对应月访问 4,800、注册 1,150 的产品规模。事件量再小,可以先只做 12 个:访问 2 个、账号 3 个、核心动作 3 个、计费 3 个、异常 1 个。少记几个不会让结论变错,记错一个会让结论变得像对的。
属性要避开内容与身份信息。 object_id 记标识,不记标题;profile_updated 记发生了更新,不记更新了什么。事件表一旦存了用户正文,删除请求就会变成一次跨系统的清理工程,而这个成本在埋点那天通常没人提。
验收看追溯,不看完整度。 合格的标志不是字典有 24 行,而是随便指一个报表数字,你能在字典里查出它来自哪个事件、用了哪个属性、排除了哪些取值。查不出来,行数再多也只是摆设。
关键参考与核验范围
核验日期:2026-10-26。以下链接用于核对事件命名、属性建模与数据结构版本化的通用做法,本文的 24 个事件与全部数字均为本系列基线的教学演示。
- Plan your taxonomy | Amplitude Docs :用于核对「先定问题与指标、再定事件与属性」的规划顺序。
- JSON Schema :用于核对属性类型、必填项与取值约束的表达方式。
- Introduction to structuring your data with schemas | Snowplow Documentation :用于核对事件与实体分离、以及 schema 版本化的做法。
- Events - Docs - PostHog :用于核对事件与属性在采集侧的最小集合与命名习惯。
引用口径。 外部链接只用于确认通用概念与命名,不引用其产品价格、套餐限制或具体实现细节;本文出现的事件数、字段数与时长均来自本系列统一基线,或由基线在本篇内四则运算得出。
核验范围。 未核验任何第三方分析工具对事件名长度、属性数量或命名风格的实际限制,也未引用其官方推荐的事件清单。object_action 是本文采用的规则,不是行业标准。
如何自行复核。 任选一个已经对外引用过的数字,从报表倒推:它用了哪个事件、按哪个属性分组、排除了哪些取值、时间窗是哪一段。四步里有一步卡住,就说明字典缺了对应的那一行。
系列导航
- 上一篇:度量从问题开始:六个决策与十二个指标
- 下一篇:埋点实现:客户端十四、服务端十与三处校验
- 回到目录:一个人的产品度量:事件字典、指标口径与决策看板
- 相关阅读:崩溃上报与版本遥测:发布后 72 小时的止损阈值
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。