订阅更新

上游 Schema 变了没通知你——怎么防御这种『看不见的故障』?

数仓开发手册 第 13 题 约 9 分钟读完

本文目录
  1. 先说结论
  2. 业务场景:某短剧出海公司的广告回传字段含义变更事故
  3. 段落一:第一层——契约(数据合同)
  4. 段落二:第二层——DQC 自动校验
  5. 段落三:第三层——告警分级
  6. 段落四:第四层——组织约束
  7. 面试高频追问
  8. 实战踩坑:一个 JSON 字段引发的长期事故
  9. 总结

先说结论

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年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器