跳到内容

第 12 卷 / 第 1 章 / 第 1 课

1.1 数据产品、事件语义与契约:数据从产生时就要可解释 ​

数据集里一列名为 success 的数字,可能表示任务最终成功,也可能只表示某一步调用成功。失败后重试是算一次任务还是多次事件,必须在分析前写清。

数据能否分析,首先取决于产生时的语义是否明确。清洗可以修格式,不能凭空恢复从未记录的业务含义。

本课目标 ​

  • 从决策问题反推数据需求;
  • 区分实体、事件、快照和指标;
  • 为数据集建立 schema、语义和质量契约;
  • 处理事件 ID、时间、重复、迟到与 schema 演进。

1. 先写使用问题,不先收集所有字段 ​

假设数据平台要回答:“过去四周不同任务类型的完成率是否下降?”需要先定义:

  • 一个任务的唯一身份是什么;
  • “完成”包含哪些状态;
  • 取消、超时和重试怎样计入分母;
  • 按创建日还是完成日归属;
  • 时区和统计截止时间;
  • 数据更新到什么延迟才算完整。

没有这些定义,SQL 能稳定地产出一个数字,却无法保证数字回答了原问题。

数据收集遵循目的限制:只采集完成明确用途所需的字段,并记录用途、责任人与保留期。“将来可能有用”不是无限收集的充分理由。

2. 实体、事件与快照 ​

三种常见记录模型:

模型一行表示什么典型字段
实体当前态某对象当前已知状态mission_id, status, updated_at
事件某时刻发生的一次事实event_id, mission_id, event_type, occurred_at
周期快照截止某时刻观察到的完整状态snapshot_date, mission_id, status

事件适合追踪变化历史,但重放和乱序处理更复杂;当前态查询简单,却可能丢失变化过程;快照便于“截至当时”的分析,但存储重复且要处理快照缺口。

同一管道可以同时保留事件和派生当前态。关键是不要把三者混进一张表后靠列名猜测。

3. 时间至少有两个维度 ​

text
occurred_at:业务事件实际发生时间
ingested_at:平台接收到该记录的时间

迟到事件可能 occurred_at 很早、ingested_at 很晚。按事件时间计算业务窗口,按摄取时间监控管道延迟。只有一个 timestamp 会让“昨天的指标为什么今天变化”难以解释。

还应明确:

  • 时间戳时区;
  • 精度与允许误差;
  • 设备时钟不准怎样处理;
  • 窗口何时关闭;
  • 迟到数据是否回补历史结果。

4. 唯一 ID 支持幂等 ​

网络重试、生产者重启和消费者恢复都会造成重复投递。若同一业务事件每次重试都生成新 ID,下游无法可靠去重。

一个事件契约可包含:

json
{
  "event_id": "evt-m1042-completed-3",
  "event_type": "mission.completed",
  "schema_version": 3,
  "mission_id": "M-1042",
  "occurred_at": "2026-08-03T09:15:00Z",
  "producer": "mission-service",
  "payload": {"result": "success"}
}

稳定 event_id 只能识别重复投递,消费者仍需把去重与结果更新原子地完成。先记录 ID 再更新结果,中途崩溃会让重试跳过未完成工作;先更新再记 ID,则可能重复副作用。应使用同一事务或有明确保证的等价方案,去重保留期还要覆盖允许的重放范围。

“Exactly once”应拆成具体范围:消息传输、处理副作用和业务结果各自是否幂等。许多系统依赖至少一次投递加幂等消费者获得业务上的一次效果。

生产端也有边界:业务事务提交与事件发布分开完成,中间崩溃可能丢事件。同事务写入 outbox、再由可重试发布器投递,可以处理这个缺口,却不会消除消费端去重需求。Kafka 投递语义说明 也区分了日志内处理与外部系统副作用。

5. 数据契约不只是字段类型 ​

一份可执行契约通常包含:

  • 字段名称、逻辑类型、是否可空;
  • 单位、枚举值和取值范围;
  • 主键或业务唯一键;
  • 事件与时间语义;
  • schema 兼容规则;
  • 新鲜度、完整性和可用性目标;
  • 数据分类与访问要求;
  • owner、消费者和变更通知流程。

例如 duration 写成整数远远不够:它是毫秒还是秒,是否含排队时间,失败任务是否为空,负值是否可能来自时钟修正,都需写明。

6. Schema 演进需要兼容策略 ​

常见变更:

  • 新增可选字段;
  • 扩大数值范围;
  • 重命名或删除字段;
  • 改变枚举含义;
  • 把一个事件拆成多个类型。

“新增字段”也可能破坏使用 SELECT * 后按列位置读取的消费者。兼容性要同时看序列化格式与真实消费者行为。

安全流程:

  1. 发布消费者可理解的新旧双格式;
  2. 生产者开始写新字段;
  3. 监控旧消费者与空值比例;
  4. 完成回填或迁移;
  5. 经弃用窗口后再删除旧字段。

schema registry 能校验结构兼容,却无法证明字段业务含义没变。语义变更需要版本、公告和消费者确认。

7. 指标也要有契约 ​

指标定义示例:

text
名称:mission_completion_rate
分子:统计窗口内进入 completed 的唯一 mission
分母:同一 cohort 内进入 terminal state 的唯一 mission
排除:测试租户、人工回放事件
时间:按 terminal event occurred_at,UTC 日
成熟期:窗口结束后 24 小时
owner:Operations Analytics

这个分母衡量已进入终态的任务中有多少完成,不包含仍未结束的任务。若四个已创建任务里有两个完成、一个失败、一个待处理,终态口径是 2/3,创建 cohort 的完成比例却是 2/4,二者不能互换。重新打开任务或后续修正,还需规定以哪个版本的终态为准。

24 小时成熟期是报告政策,不证明所有事件已经到齐。应依据迟到分布与核对结果选择,标记 provisional 窗口,并规定更晚修正到达后怎样更新。

8. 原始层不是“永远正确”的备份 ​

保留不可变原始输入有助于重放与审计,但仍可能包含:

  • 源系统已经写错的值;
  • 解码失败或截断数据;
  • 个人或敏感信息;
  • 重复与乱序事件;
  • 已变更 schema 的多个版本。

原始层应记录摄取批次、校验和、来源、schema 版本和访问控制。不可变不等于无限保留;删除义务可能要求原始、派生、备份和索引一起处置。

常见误区 ​

  • 先把数据全收进来,以后再定义指标:缺失语义无法由清洗恢复。
  • 一个 timestamp 足够:事件时间与处理时间回答不同问题。
  • 有消息队列就不会重复:重试和恢复仍要求幂等设计。
  • schema 兼容等于业务兼容:字段类型不变,含义也可能已经变化。

练习 ​

  1. 为“任务完成率”写出分子、分母、时间归属和排除项。
  2. 给任务状态变化设计事件 ID 与重试语义。
  3. 找出一个类型兼容但语义不兼容的 schema 变更。
  4. 为一张原始事件表列出最少五项 provenance 字段。

小结 ​

数据生命周期从定义事实开始,而不是从读取 CSV 开始。事件语义、双时间、稳定 ID 和数据契约决定下游能否重放、去重和解释指标。

下一课沿着这份契约建设摄取、存储、处理、发布、归档和删除流程,并把每一步变成可运营的状态转换。

Built with VitePress | Software Systems Atlas