数据质量出了事故,下游报表全错了——质量监控体系怎么建?
先说结论
数据质量监控的核心是'四性检查':完整性(数据有没有丢)、准确性(数据算得对不对)、一致性(上下游对得上对不上)、及时性(数据有没有按时产出)。
质量检查的时机很重要:不是跑完全链路最后查一次,而是每一层跑完都要查——ODS检查完整性,DWD检查准确性,DWS检查一致性。越早发现问题,影响范围越小。
数据血缘是质量监控的基础设施。出了问题要快速定位影响范围——哪些下游表受影响、哪些报表要暂停、要通知谁。没有血缘图这些全靠人肉排查。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
数据质量问题是数仓最怕的事故之一。不像服务挂了立刻能发现——数据质量问题往往是'悄无声息地错了好几天',等业务方发现报表数字不对时,已经影响了决策。我在某短剧出海公司经历过一次重大数据质量事故——DWD层的一个字段映射写反了,导致所有渠道的ROI数据交叉错误,整整错了3天才被发现。那次之后我们花了一个月建立了完整的数据质量监控体系。
四性检查规则设计
| 检查维度 | 检查内容 | 检查位置 | 告警阈值示例 |
|---|---|---|---|
| 完整性 | 数据量是否在合理范围 | ODS层 | 今日行数<昨日80%或>昨日150% |
| 准确性 | 关键字段空值率、异常值比例 | DWD层 | user_id空值率>5%、金额为负>0.1% |
| 一致性 | 上下游行数匹配、指标交叉验证 | DWS层 | DWS汇总与DWD明细差异>1% |
| 及时性 | 作业是否在SLA时间内完成 | 全链路 | P0作业超过08:00未完成 |
完整性检查:ODS层
ODS层的完整性检查是第一道防线——如果ODS数据就不完整,下面全部白搭。
-- ODS完整性检查SQL
INSERT INTO dq_check_results
SELECT
'ods_ad_click_log_di' AS table_name,
'${bizdate}' AS check_date,
'COMPLETENESS' AS check_type,
today_cnt,
yesterday_cnt,
ROUND(today_cnt / NULLIF(yesterday_cnt, 0) * 100, 2) AS ratio_pct,
CASE
-- 今天数据量<昨天80%:可能丢数据
WHEN today_cnt < yesterday_cnt * 0.8 THEN 'CRITICAL'
-- 今天数据量>昨天150%:可能重复数据
WHEN today_cnt > yesterday_cnt * 1.5 THEN 'WARNING'
-- 今天数据量=0:上游肯定出问题了
WHEN today_cnt = 0 THEN 'CRITICAL'
ELSE 'NORMAL'
END AS alert_level
FROM (
SELECT
(SELECT COUNT(*) FROM ods_ad_click_log_di
WHERE dt = '${bizdate}') AS today_cnt,
(SELECT COUNT(*) FROM ods_ad_click_log_di
WHERE dt = DATE_SUB('${bizdate}', 1)) AS yesterday_cnt
);
为什么不直接检查'行数>0'?因为行数>0不代表完整——可能只灌进来了一部分数据。跟昨天对比是最简单有效的检查方式。更精细的做法是取过去7天的均值作为基准,避免周末数据量差异造成误报。
准确性检查:DWD层
DWD层的准确性检查关注字段级别的质量——空值率、异常值比例、枚举值分布:
-- DWD准确性检查:字段级别质量扫描
SELECT
'${bizdate}' AS check_date,
COUNT(*) AS total_rows,
-- 空值率检查
ROUND(SUM(CASE WHEN user_id IS NULL OR user_id = ''
THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS user_id_null_pct,
ROUND(SUM(CASE WHEN channel IS NULL
THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS channel_null_pct,
-- 异常值检查
ROUND(SUM(CASE WHEN CAST(pay_amount AS DECIMAL(10,2)) < 0
THEN 1 ELSE 0 END) / COUNT(*) * 100, 4) AS negative_amount_pct,
ROUND(SUM(CASE WHEN event_time < '2020-01-01'
OR event_time > '${bizdate} 23:59:59'
THEN 1 ELSE 0 END) / COUNT(*) * 100, 4) AS invalid_time_pct,
-- 枚举值检查(渠道只允许已知值)
SUM(CASE WHEN channel NOT IN ('FACEBOOK','TIKTOK','GOOGLE','SNAPCHAT')
THEN 1 ELSE 0 END) AS unknown_channel_cnt
FROM dwd_user_payment_di WHERE dt = '${bizdate}';
一致性检查:DWS层
一致性检查验证上下游的数据是否匹配——DWS汇总出来的数字应该跟DWD明细对得上:
-- DWS一致性检查:汇总值 vs 明细值交叉验证
SELECT
'paid_uv' AS metric_name,
dws_value, dwd_value,
ABS(dws_value - dwd_value) AS abs_diff,
ROUND(ABS(dws_value - dwd_value) / NULLIF(dwd_value,0) * 100, 2)
AS diff_pct,
CASE WHEN ABS(dws_value - dwd_value) / NULLIF(dwd_value,0) > 0.01
THEN 'CRITICAL' ELSE 'NORMAL' END AS alert_level
FROM (
SELECT
(SELECT SUM(paid_uv) FROM dws_channel_roi_1d
WHERE dt = '${bizdate}') AS dws_value,
(SELECT COUNT(DISTINCT CASE
WHEN event_type='purchase' AND pay_status='success'
AND pay_amount > 0 THEN user_id END)
FROM dwd_user_payment_di
WHERE dt = '${bizdate}') AS dwd_value
);
数据血缘:出事时快速定位影响范围
数据质量出问题后,第一件事是搞清楚'影响了哪些下游'——这就需要数据血缘图。我们在某短剧出海公司的血缘管理用DataWorks自带的血缘分析功能,能自动解析SQL的FROM和INSERT INTO关系构建血缘图。出问题时30秒内就能定位所有受影响的下游表和报表。
数据血缘示例(出事时快速定位):
假设dwd_user_payment_di出了质量问题:
dwd_user_payment_di
+-> dws_channel_roi_1d -> ads_daily_roi_report -> Hologres大屏
| -> ads_weekly_summary -> 邮件周报
+-> dws_user_retention_1d -> ads_retention_report -> BI看板
+-> dws_user_behavior_wide_1d -> ads_user_profile -> 推荐系统
影响范围:3张DWS表 + 4张ADS表 + 3个终端应用
需要通知:投放团队、运营团队、产品团队、推荐团队
面试高频追问
追问一:质量检查本身会不会影响作业性能?
会,所以要控制检查的开销。我们的做法是:完整性检查和及时性检查几乎零开销(只查COUNT和时间戳);准确性检查用抽样或近似算法(比如用APPROX_COUNT_DISTINCT代替COUNT DISTINCT);一致性检查只对P0指标做交叉验证,不是所有指标都查。整体来说,质量检查的耗时占全链路耗时的5%以内。
追问二:质量检查发现问题后怎么处理?自动阻断还是告警后人工判断?
取决于问题的严重程度。CRITICAL级别(如ODS行数为0、DWD空值率>20%)自动阻断下游作业——宁可报表延迟也不能出错误数据。WARNING级别(如行数波动较大、个别字段空值率偏高)告警后人工判断——有些波动是业务正常现象(比如大促期间数据量翻倍)。我们的经验是:刚上线时所有检查都设成告警不阻断,观察一周后根据误报情况调整阈值,再把CRITICAL规则改成自动阻断。
追问三:你怎么衡量数据质量治理的效果?
我们用三个指标衡量:① 数据事故数(每月有多少次数据质量导致的线上问题)——从治理前每月4-5次降到治理后每月<1次;② 问题发现时间(从问题发生到被发现的时间)——从平均8小时(业务方投诉)降到平均30分钟(自动告警);③ 影响修复时间(从发现到修复的时间)——从平均4小时降到平均1.5小时(得益于血缘快速定位)。这三个数据面试时一定要准备好。
实战踩坑
我在某短剧出海公司经历的那次重大质量事故是这样的:一个同事在更新DWD的渠道字段映射时,把Facebook和TikTok的channel code写反了——Facebook的数据标成了TikTok,TikTok的标成了Facebook。因为总的数字(总UV、总收入)没变,只是两个渠道的数据互换了,我们当时的质量检查(行数检查、总量检查)完全没有捕获到这个问题。
整整错了3天,直到Facebook的运营负责人说'这周Facebook的ROI怎么突然好了50%'才被发现——是因为他看到的其实是TikTok的数据。修复方案:① 紧急修改字段映射,重跑3天的DWD→DWS→ADS全链路;② 长期方案是增加'分维度检查'——不仅检查总量,还要检查每个渠道的指标跟历史对比。
-- 教训:增加分维度质量检查
-- 治理前的检查(只查总量,漏掉了维度内的数据互换)
SELECT SUM(paid_uv) FROM dws_channel_roi_1d -- 总量没变!
-- 治理后的检查(按渠道维度分别检查跟历史对比)
SELECT
channel, today_uv, avg_7d_uv,
ROUND(ABS(today_uv - avg_7d_uv) / NULLIF(avg_7d_uv, 0) * 100, 2)
AS change_pct,
CASE WHEN ABS(today_uv - avg_7d_uv) / NULLIF(avg_7d_uv, 0) > 0.3
THEN 'WARNING' ELSE 'NORMAL' END AS alert_level
FROM (
SELECT channel,
SUM(CASE WHEN dt='${bizdate}' THEN paid_uv END) AS today_uv,
AVG(CASE WHEN dt BETWEEN DATE_SUB('${bizdate}',7)
AND DATE_SUB('${bizdate}',1) THEN paid_uv END) AS avg_7d_uv
FROM dws_channel_roi_1d
GROUP BY channel
);
-- Facebook和TikTok的UV互换后,两个渠道都会触发WARNING
总结
数据质量监控的核心是四性检查(完整、准确、一致、及时),分层检查(ODS查完整、DWD查准确、DWS查一致),以及数据血缘(出问题快速定位影响范围)。面试时讲数据质量,不要只说'我们做了质量检查'——要能说出你检查了什么、阈值怎么设、发现过什么问题以及怎么修复的。如果能量化治理效果(事故数从每月5次降到<1次),会非常加分。
质量监控搞定了,下一篇聊一个Flink实时链路的核心问题——作业挂了重启后数据重复了,Exactly-Once怎么保证?
你们有数据质量监控体系吗?遇到过最严重的数据质量事故是什么?评论区聊聊~
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长