广告行为数据:先定义事件契约,再讨论实时链路
从一条重复点击日志出发,拆解事件粒度、身份、时间、版本与数据质量验收。
Read in English →工程学习与设计笔记。示例使用合成场景;适用版本与参考资料见正文。
接入一个媒体,最容易交付的是“JSON 已经转成统一格式”;最容易留下的问题,是转换前后究竟是不是同一个业务事件。字段齐全不代表数据可用。广告行为链路的第一份交付物,应该是一份能执行、能对账的数据契约。
本文以虚构的点击上报为例讨论方案,不涉及具体公司的内部协议。
一条记录代表什么
假设客户端点击了一次广告,因为响应超时重试三次。接入层收到三条请求,业务上只有一次点击。若每次接入都生成新的 UUID,下游看到的就会是三个不同事件。
先区分四种身份:
| 字段 | 回答的问题 | 能否用于行为去重 |
|---|---|---|
| request_id | 哪次广告请求 | 通常不能,一次请求可能有多个广告位 |
| impression_id | 哪次展示机会或曝光,需按协议明确 | 不能直接代替点击标识 |
| event_id | 哪次业务行为 | 可以,但要明确来源内唯一范围 |
| delivery_id | 哪次传输尝试 | 不可以,重试应保留原 event_id |
OpenRTB 的请求对象、广告位对象与出价对象分别携带身份信息,说明竞价过程本身就有不同粒度;不能把其中一个 ID 当成所有行为的万能主键。IAB OpenRTB 2.6
这里的设计选择是:去重键使用 (source, event_type, event_id),传输追踪另用 delivery_id。如果上游没有稳定事件 ID,需要共同约定确定性生成规则,并接受其边界。简单拼接“用户 + 广告 + 秒级时间”会把用户同一秒内的两次合法操作合并,不能悄悄作为通用方案上线。
时间至少保留两条轴
一条事件应保留业务发生时间 event_time 和平台接收时间 ingest_time。加工时间可以再加,但不能覆盖原始时间。
{
"source": "demo-media",
"event_type": "click",
"event_id": "evt-demo-001",
"event_time": "2026-10-01T10:00:00Z",
"ingest_time": "2026-10-01T10:00:08Z",
"schema_version": "3",
"mapping_version": "12"
}
上述数据只用于演示。ingest_time - event_time 可以描述到达延迟,但负值不应一律丢弃:它可能来自客户端时钟偏差。应分别记录负延迟率、极晚到达率和缺失率,再按业务契约决定隔离或修正方式。修正后的时间另存字段,才能解释历史报表为什么变化。
统一事件信封可以借鉴 CloudEvents 对事件元数据的一致表达,但业务语义仍由自己的契约负责:标准不会替你定义什么算一次有效点击。CloudEvents
允许未知,拒绝偷偷猜测
“金额缺失”与“金额为零”、“用户未知”与“用户为空字符串”都不是同一件事。映射规则需要同时定义类型、单位、空值语义和枚举兜底。例如金额使用最小货币单位的整数,并独立携带币种;枚举遇到新值时保留原值及 unknown 标记,不随意映射为成功。
版本也应拆开:schema 描述目标结构,mapping 描述来源字段如何转换,rule 描述业务判断。结构没变但映射把秒误当毫秒,依然足以让全链路失真。每条输出关联这三类版本,回放时才有机会重现原结果。
我倾向把错误分成“无法解析”“缺少必要身份”“业务值不合法”“可接受但信息不全”四类。前三类进入隔离流,最后一类可以继续处理,但必须带质量标记。隔离流不是垃圾桶:要有责任归属、保留周期和修复后的重放入口。
验收不只看成功率
一个接入用例至少准备正常事件、重复投递、乱序到达、缺失 ID、未知枚举、跨天事件、超大字段与非法编码八类样本。然后检查:
- 同一事件重放两次,业务结果是否仍只产生一次;传输审计是否能看到两次。
- 每条输入能否归入成功、明确过滤或隔离,三者相加是否等于输入数。
- 目标字段能否追溯到来源字段和映射版本,默认值是否有单独计数。
- 新旧映射处理同一批样本时,差异能否按字段与原因汇总。
接入量、有效事件量、去重后事件量和最终可关联量应分开观察。把它们压成一个“成功率”,会让解析成功掩盖 ID 缺失,也会让重复请求伪装成业务增长。
最后留下可审查的契约表、样本集和错误码,而不只是一个转换函数。以后每接一个来源,都能复用相同的验证方式。
参考资料
- IAB Tech Lab:OpenRTB 2.6 ,用于核对请求、广告位、出价的对象边界;本文事件契约是独立设计示例。
- CloudEvents ,用于理解统一事件元数据的目标。