外观
数据质量闭环怎么落地
难度与前置
难度:中级
前置:有日批主题表、分区 dt、调度依赖;知道主键唯一、空值、波动校验是什么。
读完能做什么:给一张 DWD/DWS 挂分级规则,打通「告警→认领→修复→同分区复验」,并用清单验收闭环是否真的能关掉。
适用场景
- 主题表已上线,下游依赖稳定,需要发现问题能闭环,而不是只堆规则条数
- 对账、日报、看板经常「数不对」但事后说不清谁修、修没修好
- 想把质量从「人肉抽查」升级成「可阻断 / 可统计」的机制
不适用:
- 还在一次性探索取数、表随时改字段(先稳住契约再挂闭环)
- 只有离线脚本、没有调度与通知渠道(闭环缺「告警→认领」载体)
- 把所有对账都想做成实时秒级(本篇按日批分区闭环设计)
真实场景(一条)
投放 DWD dwd_ad_spend_detail_di 上线两周后,运营反馈「某渠道日报消耗比渠道后台少约 8%」。
当时已有 30+ 条 DQ 规则,但:
- 告警群里刷屏,无人认领,三天后才有人翻日志
- 开发重跑了 ODS→DWD,没记分区、没复验,第二天同问题又出现
- 事后才发现:渠道侧含赠款,我方口径不含赠款——规则从未校验「与渠道对账差异带原因码」
改造后:
- P0:主键唯一、金额非空 → 失败阻断下游 DWS
- P1:日环比波动超阈、与渠道对账差异超阈 → 告警并强制认领
- 问题单字段:表、
dt、规则、负责人、根因、修复动作、同分区复验结果 - 该 case 关闭条件:同
dt复验 P0 全过 + 对账差异落入「赠款」原因码且金额对齐
墙钟上:从「群里吵三天」变成「当日认领、当日复验关闭」。
怎么做(步骤清单)
1. 先定对象与 SLA(一张问题单的最小单元)
每条闭环至少写清:
| 项 | 示例 |
|---|---|
| 对象表 | dwd_ad_spend_detail_di |
| 分区 | dt = 业务日 |
| 出门禁点 | DWD 完成后、DWS 启动前 |
| SLA | P0 失败 15 分钟内认领;当日分区复验关闭 |
| 值班 | 主题 owner + 备份人 + 告警群 |
TIP
闭环的最小单元是「表 + 分区 + 规则」,不是「某条 SQL」。认领与复验都按这个粒度记。
2. 规则分层:P0 阻断 / P1 告警 / P2 观察
| 级别 | 含义 | 典型规则 | 失败动作 |
|---|---|---|---|
| P0 | 错了就不能给下游 | 主键唯一、核心金额非空、分区行数=0、强依赖维表缺失 | 调度失败或下游不触发 |
| P1 | 能出数但要人看 | 日环比/周同比波动、对账差异超阈、枚举突变 | 告警 + 强制认领;可带熔断开关 |
| P2 | 观察与趋势 | 新枚举出现、空值率缓慢爬升 | 周报汇总,不刷屏 |
分级口诀:
- 错了会让下游金额/主键直接错 → P0
- 可能是业务波动也可能是事故 → P1
- 暂无证据、需要积累基线 → P2
示意:主键唯一(P0)与波动(P1):
sql
-- P0:当日分区主键重复(应返回 0 行)
SELECT bill_id, COUNT(*) AS cnt
FROM dwd_ad_spend_detail_di
WHERE dt = '${biz_date}'
GROUP BY bill_id
HAVING COUNT(*) > 1
LIMIT 100;
-- P0:正式口径金额空值(应返回 0)
SELECT COUNT(*) AS null_cost_cnt
FROM dwd_ad_spend_detail_di
WHERE dt = '${biz_date}'
AND is_valid = true
AND cost_amount IS NULL;
-- P1:日环比波动(结合基线阈值,示意)
WITH today AS (
SELECT SUM(cost_amount) AS cost
FROM dwd_ad_spend_detail_di
WHERE dt = '${biz_date}' AND is_valid = true
),
yest AS (
SELECT SUM(cost_amount) AS cost
FROM dwd_ad_spend_detail_di
WHERE dt = date_sub('${biz_date}', 1) AND is_valid = true
)
SELECT
t.cost AS cost_today,
y.cost AS cost_yest,
(t.cost - y.cost) / NULLIF(y.cost, 0) AS dod
FROM today t CROSS JOIN yest y;
-- 告警条件示例:ABS(dod) > 0.3 且非大促日历日3. 挂载点:出门禁,而不是「跑完再看看」
推荐挂载:
text
ODS 完成
→ DWD 加工
→ 【DQ 出门禁】P0 全过才继续
→ DWS / ADS
→ 【DQ 对账/波动】P1 异步告警 + 认领原则:
- P0 挂在依赖边上:失败 = 下游不跑(或明确进入「熔断人工放行」流程)
- P1 可异步:不阻塞 SLA 内出数,但必须进问题单
- 规则 SQL 与主题任务同仓同口径(同一
is_valid、同一金额字段),避免「规则一套、报表一套」
4. 告警 → 认领 → 根因 → 修复 → 同分区复验
固定状态机(字段写进问题表 / 工单):
text
OPEN(告警产生)
→ CLAIMED(有人认领,记下开始时间)
→ FIXING(定位根因:源 / 口径 / 任务 / 基础设施)
→ FIXED_PENDING_RECHECK(已修数或已改逻辑)
→ CLOSED(同表同 dt 复验通过)
或 WAIVED(带审批的豁免,必须写原因与有效期)根因三选一(先归类再深挖):
| 根因类 | 例子 | 修复方向 |
|---|---|---|
| 源 | 渠道晚到、文件缺列、重复推送 | 补数、与源侧对票 |
| 口径 | 含税/赠款、时区、过滤条件变更 | 改 DWD 契约并回刷分区 |
| 任务 | 依赖漏配、并发写同一分区、逻辑 bug | 修任务 + 重跑级联 |
WARNING
修完必须同分区复验。只看「任务变绿」不算关闭;要以规则 SQL 再跑一遍该 dt 的结果为准。
5. 问题表最小字段(可复制)
sql
CREATE TABLE IF NOT EXISTS dq_issue_ticket (
issue_id STRING COMMENT '工单 ID',
table_name STRING COMMENT '对象表',
dt STRING COMMENT '业务分区',
rule_id STRING COMMENT '规则编码',
rule_level STRING COMMENT 'P0/P1/P2',
alert_ts TIMESTAMP COMMENT '告警时间',
owner STRING COMMENT '认领人',
claim_ts TIMESTAMP,
root_cause STRING COMMENT '源/口径/任务/其他',
fix_action STRING COMMENT '补数/改逻辑/豁免等',
recheck_sql STRING COMMENT '复验用的规则或摘要',
recheck_result STRING COMMENT 'PASS/FAIL',
recheck_ts TIMESTAMP,
status STRING COMMENT 'OPEN/CLAIMED/.../CLOSED/WAIVED',
note STRING
)
COMMENT 'DQ 闭环问题单(示意,可落 Hive/工单系统)';6. 基线与阈值:没有数字就不要喊「波动」
- 新主题上线前 7~14 天先跑 P2 观察,沉淀分位数再升 P1
- 大促 / 渠道切换日写入日历豁免,避免刷屏导致「狼来了」
- 对账类规则必须带差异原因码(迟到、赠款、过滤、未知),「未知」超阈才升 P0/P1
常见坑
堆 200 条规则仍漏报
无分级、无基线、全是 P2 刷屏。先保 P0 出门禁,再加少量高信号 P1。只告警无人认领
群消息 ≠ 闭环。必须有认领人、认领时限;超时升级到 backup / 主管。修数不复验
重跑变绿就关单,同分区问题次日再现。关闭条件写死:规则 SQL 对同一dt再跑通过。把对账当一次性脚本
手工 Excel 对一次就丢。对账差异应进 P1 规则 + 原因码,并可按周统计打开→关闭时长。P0 过多导致「习惯性人工放行」
出门禁失效。P0 只留真正不能错的几条;其余降级并配豁免审批。规则口径与主题口径不一致
规则扫全量、报表只算is_valid=true,告警永远对不上业务。规则与 ADS/DWS 同过滤。
如何验收(可量化)
- [ ] 对象表、分区、出门禁点、SLA、值班人写进文档或表注释
- [ ] 至少 3 条 P0(主键唯一、核心度量非空、空分区)挂在 DWD→DWS 依赖上,失败可阻断
- [ ] P1 告警能进问题单且强制认领;状态机含复验字段
- [ ] 抽一条历史问题:能从告警复盘到根因、修复、同分区复验 PASS
- [ ] 周维度可统计:打开数、关闭数、P0 平均关闭时长、WAIVED 占比
- [ ] 对账类差异带原因码;「未知」有单独阈值
sql
-- 验收 1:P0 主键重复应为 0
SELECT COUNT(*) AS dup_group_cnt
FROM (
SELECT bill_id
FROM dwd_ad_spend_detail_di
WHERE dt = '${biz_date}'
GROUP BY bill_id
HAVING COUNT(*) > 1
) t;
-- 验收 2:问题单闭环完整性(示意)
SELECT
status,
COUNT(*) AS cnt,
AVG(unix_timestamp(recheck_ts) - unix_timestamp(alert_ts)) / 3600.0 AS avg_hours_to_close
FROM dq_issue_ticket
WHERE dt >= date_sub('${biz_date}', 7)
GROUP BY status;
-- 期望:CLOSED 占大多数;OPEN/CLAIMED 不跨日堆积;关闭单 recheck_result = PASSbash
# 验收 3:调度出门禁(示意:DQ 任务失败则下游不触发)
# 用你们的调度器检查依赖边:dwd_xxx_dq_p0 → dws_xxx
# 人为注入一条重复主键后,确认 DWS 未启动且告警已产生相关面试题
题库小程序链接待定;先用站内锚点占位。
- [ ] 题:质量闭环最少要写清哪些出门禁信息?(考察点:对象表/分区/出门禁点/SLA/值班人)→ 占位:
#todo-interview-closed-loop-gate-info - [ ] 题:P0 挂在依赖上应能做什么?如何人为验证?(考察点:失败可阻断下游 + 注入重复主键后 DWS 不启)→ 占位:
#todo-interview-closed-loop-p0-block - [ ] 题:一条历史问题怎样才算闭环?(考察点:告警→根因→修复→同分区复验 PASS)→ 占位:
#todo-interview-closed-loop-ticket-close - [ ] 题:周报看哪些数字证明闭环在转?(考察点:打开/关闭/P0 平均关闭时长/WAIVED 占比)→ 占位:
#todo-interview-closed-loop-weekly-metrics
改写说明:结构参考 Wood 原创质量/面试实践,已改写成可执行清单;未包含未公开公司数据与内部工单系统名。