管道契约 — 五份可执行的保证
这一章讲三件事: 管道契约是什么——schema 只管形状,契约管身份; 五份保证逐个拆开:结构、意义、行为、安全、来源; 以及 06 章那个「只数登录」的捷径,在契约世界里死于哪一步。 承上:06 章说要「证明写入前符合定义」;这一章就是那份「证明」的格式。
1. 契约不是 schema:schema 管形状,契约管身份
这一节定位「管道契约」在整个体系里的位置。
管道契约是工程师在写任何转换逻辑之前创建的正式、机器可读的规格; 它是数据生产者与平台之间的一份约束协议——平台据此自动执法1。
书对它和 schema 的区分是一句话的事,但值得停一下:schema 描述数据的形状 (有哪些列、什么类型);契约描述数据的身份——它是什么意思、随时间怎么表现、 从哪来、必须受什么管。契约给平台一张「意图+义务」的完整图,执法才可能自动化2。
契约由四个核心组件组成,本章 §2-§5 逐个拆3。先给一张总览,
例子全部沿用 06 章那个 usage_metrics(用量指标)数据产品——
它每天产出一份「按天统计的活跃用户」:
| 组件 | 保证的是什么 | 拦下什么 |
|---|---|---|
| 结构+意义(§2) | 形状对、含义对 | 类型漂移;「只数登录」的捷径实现 |
| 行为(§3) | 按时产出、数值健康、到期清理 | 迟到;环比暴涨;永久堆积 |
| 安全(§4) | 敏感数据只在被批准的场景使用 | 个人信息漏进群聊 |
| 来源(§5) | 只用声明过的输入,血缘永不失真 | 偷偷换源;退役时炸掉不知情的下游 |
2. 组件一:结构+意义 — 把 06 章的定义变成可执行引用
这一节是本章主走查的第一段:契约怎么让「独立活跃用户」的语义真正长在管道上。
先看结构这一半。在湖仓架构里,schema 不只描述列名,它约束数据落地那一刻的 存储、校验与解释:哪一列类型不对、必填列缺失、分区格式不一致——写入当场拒绝, 而不是等下游消费了错误数据才发现4。书说这能整类消灭那些日积月累的漂移: 一串字符渗进数字列、必填列出现空值、分区格式不一致把数据放错抽屉4。
但形状对了不代表意思对。契约的另一半是语义定义:每个字段不只声明类型, 还挂上它在 03-04 章定义好的业务含义——一个机器可解释的引用,不是注释5。 书给的 YAML 契约(YAML:一种「标题: 内容」加缩进的规格文件格式,人和机器都能读)长这样5:
# pipeline_contract.yml(节选)
data_product:
name: usage_metrics # 数据产品名
schema:
- name: date
type: date
required: true
semantic_definition: Dimensions.UsageDate # 「这是报表日期」
- name: active_users
type: bigint
required: true
semantic_definition: Metrics.UniqueActiveUsers # ← 关键引用
# 含义:登录过且完成过一次有意义操作的去重用户数
semantic_definition: Metrics.UniqueActiveUsers 这一行就是 06 章那句
「登录且有有意义动作」的机器可解释引用。有了它,平台在提交契约和实现代码时
能做三件执法6:
- 治理路由:把变更送交给「独立活跃用户」定义的守护人审——业务意图由定义的主人守;
- 血缘校验:确认管道用的是经过批准、可信的上游源;
- 单测强制:必须先有测试证明「有意义动作」的逻辑实现正确,变更才准部署。
主走查:30 分钟窗口。 书配的 SQL 实现(数据库查询语言)把这个定义 拆成三步,走一遍(时间值为演示编的)7:
第 1 步 取当天所有登录事件:
u1 登录于 10:00 u2 登录于 10:05
第 2 步 取当天所有动作事件:
u1 的动作在 10:20 u2 的动作在 11:40
第 3 步 两表对号连接,条件:动作发生在登录之后、且不晚于登录+30 分钟
u1:10:20 落在 10:00-10:30 之内 → 算「合格登录」✓
u2:11:40 在 10:05+30 分钟之外 → 不算 ✗
第 4 步 按天去重计数:active_users = 1(u1)。
反事实:如果实现成「只数登录」, 这一天的输出是 2。
差距就是语义;差距由契约的执法拦下,而不是由人眼。
书对这套机制的总结是:有人想抄「只数登录」的捷径,执法在第一行数据写入之前 就拦下管道——不是等仪表盘崩了再查8。schema 保证类型和分区漂不了; 语义定义让意图可验证、可审计。这两份保证流进检索,RAG 系统就不用猜一个指标 代表什么,也不会围绕一个错数编故事9。
3. 组件二:行为 — 运营保证
这一节回答:身份对了之后,怎么保证它一直可信——「今天对」不等于「明天还对」。
运营保证规定数据随时间如何表现:何时更新、健康怎么监控、出事谁管10。 它们写进契约,因此可测、可执行、对所有下游可见。书在同一份 YAML 上继续加11:
# 续 pipeline_contract.yml
operational:
schedule:
cadence: "daily" # 每天一次
at_time_utc: "08:00"
sla: # 服务水平承诺:对「多快」的正式承诺
freshness:
unit: "hour"
value: 4 # 数据龄不得超过 4 小时
accountability:
owner: "team-engagement-metrics@example.com"
alert_severity: 3
quality_monitors: # 每次运行都必须成立的条件
- "active_users IS NOT NULL"
- "active_users_day_over_day_change < 0.20"
retention:
unit: "day"
value: 180 # 保留 180 天,到期清理
四块的分工12:新鲜度由调度+承诺构成一条可自动校验的硬指标; 责任人给了告警一条明确的去路;质量监控声明每次运行必须成立的条件—— 比如「活跃用户环比变化不得超 20%」,超额即报警,防止 06 章那种虚高悄悄溜过; 保留期保证数据不超期服役,免得检索抓到过期上下文。
这些保证由平台自动执行:编排调度、持续校验、越线升级。同样重要的另一半是 平台把执行结果发布出来:每个数据产品带一个实时健康指示—— 它现在有没有违约。RAG 系统运行时就能查:某数据集迟到了、某个关键检查挂了, 系统可以换一个源,或者承认「此刻给不出可靠答案」13。
02 章「昨天的数」那桩案例在这里得到机制层面的解释:新鲜度写进契约、 检索时强制,就是那桩事故的修复方案的通用形。
4. 组件三:安全 — 治理规格
这一节把权限和政策也写进同一份契约。
书在这里给 usage_metrics 加了一列来演示敏感数据怎么处理14:
# 新增列 + 治理块
schema:
- name: user_id
type: string
semantic_definition: Dimensions.UserID
privacy_tag: "PII" # 列级:这是个人信息
governance:
classification: "Confidential" # 资产级:整份数据的敏感度
scenarios: # 允许使用的业务场景
- "e06d191c-830f-44bf-93cd-7c04e5d78b1a"
- "6e5f815e-f4ad-49f2-ae8d-dfcb984ffe70"
三行各管一层15:列级隐私标签让平台在数据被批准场景之外访问时, 自动做字段级保护(遮蔽或删敏);资产级分类给整份数据一个总敏感度—— 检索系统可以据此阻止「Confidential(机密)」级数据出现在群聊这类不安全语境; 场景清单把数据集和被批准的使用语境挂钩——这就是 02 章说的 SBAC 的落点: 按意图而不是按身份授权。书预告场景授权的展开在最终版的第 6 章(未出版)15。
5. 组件四:来源 — 数据血缘
这一节补上最后一块:数据从哪来,也要声明,而且要被执法。
先看血缘(数据血缘:一份「谁依赖谁」的全图)回答的四类问题—— 退役一份数据会断什么?指标不对该查哪?敏感数据怎么流的能证明吗? 缺陷修好后只重算受影响的行不行?16
书的关键动作是把血缘声明在契约里,而不是事后推断16:
# 续 pipeline_contract.yml
lineage:
inputs:
- name: "app_events"
version: 2
time_range:
window: 1
unit: "day" # 只滚动读最近 1 天
mode: "rolling"
columns: ["event_timestamp", "user_id", "event_type"]
声明之后,执法才开始:平台按 lineage 块自动生成连接已批准输入的代码, 工程师只写转换逻辑;查询没声明的数据集、引用没声明的列,直接被拦—— 因为这些动作在契约之外17。书给了一句很硬的保证:管道的实际血缘 永远不可能偏离契约里声明的血缘17。
由此解锁五个运营能力18:带出处的权威检索与可解释回答; 上游修复后的自动重算(只算受影响的);上游改列名时,依赖方契约不更新就 不让那个版本部署(安全部署);指标可疑时顺依赖图分钟级定位根因; 以及上游违约时自动压制下游告警的噪声(免得告警刷屏)。
6. 拼起来:五份保证,一份 YAML
这一节收拢。至此契约的完整身份是19:
结构(它长什么样) + 意义(它是什么意思) + 行为(它如何随时间表现)
+ 安全(谁在什么场景可用) + 来源(它从哪来)
图说:五份保证写在同一份 YAML 里,不是五份各自漂移的文档;
书特意强调:它们不是目录里的被动描述,是数据全生命周期里被验证、
被执行的活性规则。
06 章的承诺在这里兑现:目标不再是「描述希望正确的数据」, 而是「证明数据在写入之前符合定义」——证明的格式,就是契约; 执行证明的,是平台(08 章讲)。