别指望运气,数据契约得进CI
从凌晨三点的schema告警,到删除请求在湖仓里追杀二十多处副本,数据平台的矛盾正在从'建起来'转向'管起来'。本文拆解数据契约的两条执行路线——入口把门的Schema Registry与提交把门的CI测试,判断谁在领跑、谁在陪跑,并给出务实组合拳建议。
凌晨三点,PagerDuty 响了。下游报表团队的工程师从床上爬起来,发现管道挂了——上游把 user_id 从整数改成了字符串,Slack 里的变更公告没人看,三条财务报表模型一起遭殃。
这不是段子,这是大多数数据工程团队的日常。我的判断很直接:数据契约的价值不在于写没写,而在于有没有被强制执行;而执行的最佳位置,不是运行时,是 CI。
🌙 一、凌晨三点的告警,暴露的是'猜测式协调'
所谓'协调',很多时候只是'互相猜'的礼貌说法。
先复盘一下这场典型事故。上游工程师发布了一个数据作业,CI 全绿,dbt 测试通过,推送上线,喝杯咖啡,感觉自己是个称职的工程师。然后,他的服务决定把 user_id 改成字符串。Schema Registry 更新了,Slack 里也发了公告——但没人读。结果,下游仪表盘团队在凌晨被告警叫醒,管道干掉了三条关键的财务报表模型。
注意这里的细节:该做的流程都做了。注册表更新了,公告发了,测试也绿了。但变更依然像一颗自由落体的炸弹,砸穿了所有依赖它的下游。问题出在哪?出在所有环节都是'通知',没有一个是'拦截'。
素材里那句话说得很狠:数据契约不是花哨的 YAML 文件,它是你和一个调试 JSON 解析错误的周末之间唯一的屏障。我把它翻译成架构师的语言——数据契约的本质是接口工程化。应用开发里,API 变更要过版本管理、要过评审、要跑兼容性测试;数据接口凭什么只靠一条 Slack 消息?
据多位在一线数据团队待过的朋友私下聊,这种'契约写在文档里、执行全靠自觉'的状态,在中小团队里几乎是默认配置。团队规模小的时候靠默契还能撑,一旦生产者和消费者分属不同部门,默契就变成了赌博。所以真正的问题从来不是'要不要契约',而是——在哪里强制执行契约。
⚙️ 二、两条路线:入口把门,还是提交把门
运行时检查,本质是房子烧起来之后才报警。
业界目前真正可用的执行路线只有两条。
第一条是 Schema Registry 式执行,代表是 Confluent/Avro 和 AWS Glue Schema Registry。它把契约当成摄入层的门卫:生产者的消息不匹配注册表,消息直接在 broker 层被丢弃或拒绝。好处是硬,物理上拦住了;代价是错误已经到了生产边界,坏数据可能已经在队列里排队了。
第二条是 CI 级契约测试,代表是 Great Expectations、dbt-checkpoint 这类工具,核心思路是 schema-as-code——契约就是仓库里的代码,跟着 pull request 走。生产者改 schema,CI 自动跑兼容性测试,不兼容就阻断合并。错误在合并之前就死了,根本到不了生产。
两条路线的差异,值得摆在一张表里看清楚:
| 维度 | Schema Registry 入口把门 | CI 级契约测试 |
|---|---|---|
| 拦截时机 | 消息到达 broker 时 | 代码合并之前 |
| 失败成本 | 消息被拒、队列可能已受污染 | 一次构建失败,零生产影响 |
| 覆盖面 | 主要覆盖流式摄入 | 可覆盖作业、模型、下游依赖 |
| 反馈速度 | 上线后才知道 | 合并前几分钟拿到结果 |
| 适用场景 | 流式管道兜底 | 变更管控的主防线 |
我的判断是:CI 执行应该当主防线,Registry 当运行时的最后兜底。理由很简单——修复成本随时间指数级上涨。合并前发现,改一行代码;上线后发现,改代码、修数据、道歉,三件套一个不少。靠运行时检查守住底线的团队,本质上还是在指望运气。
📊 三、工具格局:谁在领跑,谁在陪跑
技术门槛越高的方案,往往离事故越远。
把当前的主流做法放进四象限里看,格局其实相当清晰:
几个值得展开的判断:
CI 契约测试是领跑者。 它技术含量不低——要把契约变成代码、要接入 CI/CD、要定义兼容性规则——但它解决的是最贵的那类问题:生产事故。投入产出比决定了它会成为标配。
Registry 网关稳居第一象限右侧。 Confluent 生态的成熟度摆在那里,但单独使用时它只守住了流式摄入这一个口子,所以更合理的定位是兜底层,而不是唯一防线。
自研契约平台是潜力股,也是陷阱。 大厂自研的案例听着都香,但对大多数团队,自研意味着用三个月搭一个别人开源方案八成功能的东西。除非契约复杂到开源工具装不下,否则别碰。
运行时检查和人工协调,是跟随者和存量玩家。 Great Expectations 这类工具做运行时数据质量监控是好手,但把它当契约执行机制,就是承认坏数据可以进生产、只要事后能发现。至于纯靠 Slack 公告的团队——他们还没意识到自己每天在赌博。
一句话:别把'发现问题的能力'误当成'防止问题的能力'。
🗑️ 四、比契约更难的:别人要求你删数据
契约管的是'别改坏',删除请求考的是'你到底知道自己有什么'。
如果说契约执行考验的是变更管控,那删除请求考验的就是平台家底。素材里那个基于 Google Cloud 的 medallion 湖仓案例,把这件事的残酷程度量化得很直白:一个只下过一单的客户提出删除请求,他的数据散落在二十多个地方。
有些位置很好找,比如订单表和它在青铜层的副本。有些位置极其阴险——给复制供数的变更表、月末快照、某个客服查退款时跑过的 SQL 文本、还有一张专门接住管道报错数据的表。更麻烦的是,每一处都有自己的保留规则,好几处在 DELETE 成功之后还会把数据再留几周。
这个案例里有两个区分特别专业:一是删除数据与法定保留数据的分离——大量订单数据因为法律义务本来就该留,不能一锅端;二是青铜层的双重身份——它既是重放(replay)的来源,又是个人数据以最原始形态保留最久的地方。重放能力和可删除性在这里正面冲突,必须靠设计化解,而不是靠侥幸。
把这套流程排成时间计划,你会看到它根本不是一条 SQL,而是一条流水线:
我的判断是:删除请求是数据平台进入'运营时代'的试金石。建平台的时候大家比的是架构图好不好看;跑平台的时候,比的是你能不能在可计算的时间内,对任何一条数据回答'你在哪、你何时走'。契约和删除看似两件事,内核是同一个——把治理从文档和口头承诺,变成可执行、可验证的机制。
小结
回到凌晨三点那通电话。它提醒我们:数据平台最有价值的部分,从来不是那些运行良好的管道,而是在坏东西出门之前拦住它的那道门。数据契约进 CI,是给'改'装上门禁;删除流水线,是给'删'装上门禁。未来两三年,'契约即代码、删除即流水线'会像代码评审一样成为数据团队的默认动作。还在靠 Slack 公告和运气的团队,该动起来了——运气不是架构,从来不值得依赖。