上游 Schema 变了没通知你——怎么防御这种『看不见的故障』?
本文目录
先说结论
Schema 变更防御的四层:契约层(上下游签 schema 合同)→ 校验层(DQC 每天跑)→ 告警层(异常立刻通知)→ 组织层(变更评审流程)。四层缺一层都可能漏。
最致命的不是字段删除,是字段含义变了。字段还叫 revenue、类型还是 decimal,但原来是美元现在是当地币种——这种变更技术上完全检测不到,只能靠组织约束。
数据湖的 Schema Evolution 不是万能药。Iceberg / Delta 能『兼容』schema 变更,但不能帮你发现『含义』变了。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
Schema 变更没通知下游是数仓里最让人抓狂的事故类型——表面上任务一切正常,数据悄悄错了,业务方发现时已经错了几天。面试官问这道题想考两件事:(1)你有没有被这种事故咬过;(2)你能不能设计出防御机制。能把『契约 + 校验 + 告警 + 组织约束』一整套讲出来,这题就到了资深水平。
业务场景:某短剧出海公司的广告回传字段含义变更事故
我在某短剧出海公司真实遇到过这个经典事故。上游广告系统的回传字段 `ad_revenue` 原来是美元,某次版本升级后变成了『当地币种金额』,同时加了 `currency` 字段标识。上游同事觉得『加了新字段就是向前兼容』,没通知下游。
我们的 DWD 照旧把 `ad_revenue` 当美元算,结果海外广告 ROI 看板当天虚高 3 倍——印尼盾的广告收入直接当美元计算。业务方炸了一周才发现,投放组已经按错误数据调整了预算。
-- 事故前后的 schema 对比
-- 上游原始 schema(v1):
CREATE TABLE ads_report_v1 (
campaign_id BIGINT,
ad_revenue DECIMAL(10,2), -- 美元
...
);
-- 上游变更后 schema(v2,悄悄上线):
CREATE TABLE ads_report_v2 (
campaign_id BIGINT,
ad_revenue DECIMAL(10,2), -- 含义变了!成了当地币种金额
currency STRING, -- 新加字段
...
);
-- 我们的 DWD SQL(没察觉):
INSERT INTO dwd_ad_report
SELECT campaign_id, ad_revenue AS revenue_usd -- 还当美元用
FROM ods_ads_report;
-- 结果:印尼的 campaign 看起来收入暴涨 15000 倍
-- (因为 1 美元 = 约 15000 印尼盾)
段落一:第一层——契约(数据合同)
第一层防御是在上下游之间建立明确的数据契约。契约里明确:字段名、类型、单位(!)、业务含义、允许的值范围、变更流程。
# 数据契约模板(放在 git 仓库,上下游共同维护)
table: ads_report
owner: ads_team # 上游负责人
consumers:
- dwh_team # 我们
- bi_team
- risk_team
fields:
- name: campaign_id
type: bigint
nullable: false
description: 广告活动ID
- name: ad_revenue
type: decimal(10,2)
nullable: false
unit: USD # 单位明确(这次事故的关键!)
description: 广告收入(美元)
change_policy:
- 新增字段:提前 3 天通知所有 consumers
- 删除字段:提前 14 天通知 + consumers 签字
- 含义变更:禁止(改名建新字段)
关键一点:契约必须由上下游共同 review,不是上游单方面决定。契约变更走 Pull Request 流程,所有 consumers 必须 approve。
段落二:第二层——DQC 自动校验
契约是人读的,DQC 是机器校验的。每天任务跑完自动校验 schema 是否符合契约。
-- DQC 校验模板(每日定时跑)
-- 校验 1:字段存在性
SELECT
CASE WHEN COUNT(*) = 0 THEN 'FAIL: missing column ad_revenue'
ELSE 'PASS' END AS result
FROM information_schema.columns
WHERE table_name = 'ads_report' AND column_name = 'ad_revenue';
-- 校验 2:字段类型
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'ads_report'
AND data_type != 'decimal'; -- 期望类型
-- 校验 3:数值范围(防止单位偷偷变了)
SELECT 'FAIL: revenue out of range' AS result
FROM ads_report
WHERE dt = '${bizdate}'
AND ad_revenue > 1000000; -- 单个 campaign 日收入不会过百万美元
-- 如果触发,可能是单位变了
-- 校验 4:环比异常
SELECT SUM(ad_revenue) AS today,
(SELECT SUM(ad_revenue) FROM ads_report WHERE dt = DATE_SUB('${bizdate}', 1)) AS yesterday
FROM ads_report WHERE dt = '${bizdate}';
-- 差异超过 20% 告警
段落三:第三层——告警分级
DQC 发现问题之后要分级处理,不是全部都电话告警。
| 问题类型 | 告警级别 | 处理时限 |
|---|---|---|
| 字段消失 | P0 电话 | 立即 |
| 字段类型变了 | P0 电话 | 立即 |
| 数值范围异常(可能含义变) | P1 短信 | 1 小时 |
| 环比超阈值 | P1 短信 | 2 小时 |
| 新增字段 | P2 群消息 | 次日 |
段落四:第四层——组织约束
技术手段之外,组织层面的约束是最后防线。我在某短剧出海公司推动过两件事:(1)上下游 schema 评审群——任何 schema 变更必须在群里发公告;(2)schema 变更 checklist——上游提交变更时必须勾完所有 consumer 的 ACK。
Plain
Schema 变更 Checklist(上游必须完成):
□ 数据契约 PR 已提交并通过所有 consumers review
□ 在 schema 变更群公告,至少 3 个工作日
□ 所有 downstream consumers 已 ACK 确认
□ 变更包含单元测试(字段存在、类型正确、范围合理)
□ 回滚方案明确(如何快速切回 v1)
□ DQC 规则已更新
任何一项没打勾,变更不能上线。
面试高频追问
追问一:Iceberg / Delta 的 Schema Evolution 能解决这个问题吗?
部分解决。数据湖格式能做到『新增字段』『字段改名』『类型加宽』这些结构层面的兼容,下游老 SQL 仍能跑。但解决不了『字段含义变了』——这是语义层面的问题,任何数据格式都拦不住。所以数据湖 + 契约 + DQC 是三件套,不能只靠一个。
追问二:你们公司真的能执行 schema 评审流程吗?上游嫌麻烦怎么办?
说实话最难的是这一层。我的做法:(1)把评审流程做得轻——用 GitHub PR,上游提交 YAML 变更,所有 consumers 自动收到 review 请求;(2)出过一次事故的团队对流程的抵触会明显降低——事故本身就是最好的推动;(3)最关键的是业务方背书——让最终 consumer(产品、运营)知道有这套流程保护他们的数据,他们会反过来帮你推。
追问三:如果已经出了事故,怎么修数据?
取决于事故类型。字段含义变了的场景(比如我那个汇率事故):(1)先确认从哪天开始错、影响了哪些下游表;(2)根据新 schema 重写 DWD 的转换逻辑;(3)按新逻辑回刷受影响日期的 DWD / DWS / ADS;(4)通知业务方回刷范围和预期完成时间;(5)跑完做 DQC 一致性校验;(6)事后复盘并更新契约防止再发生。整个过程一般 1-3 天。
实战踩坑:一个 JSON 字段引发的长期事故
我还踩过一个更隐蔽的坑:上游往 JSON 字段 `ext` 里悄悄加了新 key,我们下游 JSON_EXTRACT 某个 key 时一直返回 NULL。这种事故不会任何报错、不会 schema 检测告警、甚至数值范围都可能还正常(就是那一列是 NULL),半年都没人发现。
发现的契机是业务方问『为什么这个指标半年没有数据』——追溯才知道是 JSON schema 变了。从那以后我们对 JSON 字段的 key 也做契约管理,DQC 加了 『JSON 解析成功率』 校验:如果 JSON_EXTRACT 的非 NULL 比例突降,立即告警。
-- JSON schema 变更检测:解析成功率
SELECT
COUNT(*) AS total,
COUNT(JSON_EXTRACT(ext, '$.campaign_tag')) AS extract_succ,
ROUND(COUNT(JSON_EXTRACT(ext, '$.campaign_tag')) * 100.0
/ COUNT(*), 2) AS succ_rate
FROM ods_ads_report
WHERE dt = '${bizdate}';
-- 如果 succ_rate 从昨天的 99% 掉到今天的 5%
-- 说明 JSON 里的 campaign_tag key 被改名或删除了
总结
Schema 变更防御题是数仓面试里的 SRE 气质题,答好了能让面试官觉得『这人是真的扛过线上』。关键是讲出四层防御(契约 / DQC / 告警 / 组织),加上『字段含义变了技术拦不住』的认知,再补一两个真实踩坑(汇率含义变 / JSON schema 变),这题稳稳 9 分。
下一篇:『一个 1200 行的 SQL 你怎么 code review?』——生产问题收官,同时也是管理者最爱考的一道题。
你遇到过最离谱的『上游悄悄变了』事故是什么?花了多久才发现?
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长