订阅更新

DQC规则写了200条还在漏报——校验规则到底怎么设计?

数据质量手册 第 1 题 约 10 分钟读完

本文目录
  1. 先说结论
  2. 业务场景:我接手的200条规则为什么70%是噪声
  3. 五类DQC校验规则模板
  4. 规则一:基于历史基线的动态空值率校验
  5. 规则二:业务勾稽——最有价值但最容易被忽略的一类
  6. 规则分级与响应SOP
  7. 面试高频追问
  8. 实战踩坑:一次因DQC过严导致全链路停摆
  9. 总结

先说结论

DQC规则要按「五类校验维度」来设计:空值率、唯一性、值域、参照完整性、业务勾稽。每条规则必须明确三件事:校验对象、阈值、严重等级。规则不是越多越好,而是越精准越好——一条好规则胜过十条噪声规则。

更重要的是分级响应:P0(阻断下游)、P1(告警必看)、P2(趋势观察)。把规则按业务重要性分级,才能让告警真正有人看。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

DQC(Data Quality Check)是数据质量的第一道防线。但很多团队的DQC一上线就陷入两个极端:要么规则写得太严,每天100+条误报,值班同学疲于奔命,慢慢就开始忽略告警;要么写得太松,真出事的时候一条没报。我在出海硬件公司接手数据质量体系时,前任留了200多条DQC规则,70%是无效告警。今天就聊聊DQC规则到底怎么设计才是有效的。

业务场景:我接手的200条规则为什么70%是噪声

某出海硬件品牌的数据质量平台上,前任配了200多条DQC。我做的第一件事是把过去30天的告警拉出来分析,结果触目惊心:

• 单日触发3000+次告警,但P0真实故障只有2次

• 70%告警来自「空值率>5%」这种粗暴阈值——可有些字段本来业务上就允许大量为空

• 25%告警来自「数据量同比波动>20%」——可周末本来就低、节假日本来就高

• 真正应该报但没报的是某次上游字段含义变了,新口径下原指标失真,没有任何规则能发现

-- 接手时看到的典型「无效规则」示例
-- 问题1:阈值一刀切,没考虑业务特性
SELECT COUNT(*) FILTER (WHERE email IS NULL) * 1.0 / COUNT(*) AS null_rate
FROM dwd_user_register
WHERE dt = '${bizdate}';
-- 报警条件:null_rate > 0.05 ❌
-- 实际:海外用户很多用手机号注册,email空值率长期稳定在30%
-- 这种规则每天都报,但每天都是「正常」
 
-- 问题2:没有同比/环比基线
SELECT COUNT(*) AS row_cnt FROM ods_order WHERE dt = '${bizdate}';
-- 报警条件:row_cnt < 100000 ❌
-- 实际:周末订单量本就低于工作日,假日更低

五类DQC校验规则模板

我重写了一套规则分类,所有DQC必须落到这五类之一,否则不予上线:

类别校验内容典型规则适用场景
空值率(Completeness)字段非空比例user_id为空率<0.1%主键、业务关键字段
唯一性(Uniqueness)主键/业务键不重复order_id重复条数=0事实表主键、维度表自然键
值域(Validity)字段在合理范围amount BETWEEN 0 AND 1e8金额、状态枚举、日期范围
参照完整性(Integrity)外键能关联到user_id在dim_user中存在率>99%事实表与维度表关联
业务勾稽(Consistency)指标间逻辑关系支付金额=订单总额-优惠金额财务、库存、跨表对账

写规则的三要素:校验对象(具体到表+字段)、阈值(含基线策略)、严重等级(P0/P1/P2)。少一项都不算合格规则。

规则一:基于历史基线的动态空值率校验

不要用固定阈值,要用「过去N天的P95/P99」做动态基线。我在出海硬件项目里这样写:

-- 动态基线:过去30天空值率的P95作为告警线
WITH baseline AS (
  SELECT
    PERCENTILE_APPROX(null_rate, 0.95) AS p95_null_rate,
    AVG(null_rate) AS avg_null_rate,
    STDDEV(null_rate) AS std_null_rate
  FROM dq_metrics_history
  WHERE table_name = 'dwd_user_register'
    AND column_name = 'email'
    AND dt BETWEEN DATE_SUB('${bizdate}', 30) AND DATE_SUB('${bizdate}', 1)
),
today AS (
  SELECT COUNT(*) FILTER (WHERE email IS NULL) * 1.0 / COUNT(*) AS today_null_rate
  FROM dwd_user_register WHERE dt = '${bizdate}'
)
-- 仅当今日空值率超过历史P95,且超过均值2个标准差时才报警
SELECT today_null_rate, p95_null_rate FROM today, baseline
WHERE today_null_rate > p95_null_rate
  AND today_null_rate > avg_null_rate + 2 * std_null_rate;

适用场景:空值率长期稳定但偶尔波动的字段,比如海外业务的email、phone等可选字段。

不适用:强约束字段(user_id、order_id),这种应该直接=0%空值,不需要动态基线。

规则二:业务勾稽——最有价值但最容易被忽略的一类

空值/唯一性这种是「形式校验」,业务勾稽才是「真校验」。出问题的数据往往形式上没毛病,但业务逻辑算不通。举个我踩过的真实例子:

-- 财务勾稽:支付金额一定 = 订单金额 - 优惠 - 退款
-- 任何一笔不满足都说明上游表数据出了问题
SELECT order_id,
       order_amount, discount_amount, refund_amount, paid_amount,
       (order_amount - discount_amount - refund_amount - paid_amount) AS diff
FROM dwd_order_payment
WHERE dt = '${bizdate}'
  AND ABS(order_amount - discount_amount - refund_amount - paid_amount) > 0.01;
 
-- 库存勾稽:今日期末 = 昨日期末 + 入库 - 出库
SELECT sku_id,
       lag_stock + inbound - outbound AS expected_stock,
       end_stock,
       end_stock - (lag_stock + inbound - outbound) AS diff
FROM dws_sku_stock_daily
WHERE dt = '${bizdate}'
  AND ABS(end_stock - (lag_stock + inbound - outbound)) > 0;

我在出海硬件公司用这类规则发现过两次重大问题:一次是某个促销活动的优惠金额没扣,导致GMV虚高1.2%;一次是退款表延迟到达,下游报表少了200万退款金额。这两个问题用空值/唯一性都查不出来,只有业务勾稽能发现。

规则分级与响应SOP

等级判定标准响应方式示例
P0 阻断影响下游核心指标/财务立即阻断下游 + 电话告警GMV勾稽不平、主键重复
P1 必看影响业务可用性钉钉@值班 + 1小时内响应空值率超基线、值域越界
P2 观察趋势性、非阻断进日报、周观察数据量小幅波动、长尾字段质量

分级最大的好处是把告警从「狼来了」变成「真出事大家都信」。在出海硬件公司治理后,P0告警从每周5+次降到月均1次以下,每次P0值班同学都会立刻处理。

面试高频追问

追问一:DQC规则放在ETL任务前还是任务后?

两边都要放,但作用不同。任务前做「输入校验」(上游表是否就绪、上游空值率是否异常),目的是Fail Fast,避免基于脏数据继续算。任务后做「输出校验」(产出表的勾稽、唯一性),目的是阻断下游。两者结合才能防止脏数据扩散——这就是数据质量「左移+右控」的思路。

追问二:规则太多跑不动怎么办?

第一,按表的重要性分级,P0表所有规则都跑、P2表只跑核心规则;第二,把高频校验下沉到引擎层(比如建表加CHECK约束、用Hologres的列存储统计加速COUNT DISTINCT);第三,引入采样校验,10亿行的表对1%采样校验,结果置信度足够而成本只有1%。

追问三:业务规则总在变,DQC规则怎么维护?

把规则做成「配置化」而不是「硬编码SQL」。我们把每条规则抽象成{表名, 字段, 校验类型, 阈值表达式, 等级}的配置,用YAML或数据库存储,一个统一的执行器去调度。这样业务变更只需改配置,不用改代码——这也是Great Expectations这类工具的核心思想。

实战踩坑:一次因DQC过严导致全链路停摆

有次我配了一条「dim_user表user_id唯一性校验」作为P0阻断规则。某天上游CDC同步出了点延迟,dim_user表里出现了同一个user_id两条记录(一条旧值、一条新值),DQC立刻阻断了下游所有20+张表的产出。我冲到工位发现:其实这种重复在业务上是允许的——CDC的临时窗口期可能存在,下游用ROW_NUMBER取最新一条就能解决。

-- 错误的规则:直接卡死
SELECT COUNT(*) - COUNT(DISTINCT user_id) AS dup_cnt
FROM dim_user WHERE dt = '${bizdate}';
-- 阈值:dup_cnt = 0 ❌(CDC窗口期会误伤)
 
-- 修正后的规则:允许少量临时重复
WITH dup AS (
  SELECT user_id, COUNT(*) AS c FROM dim_user
  WHERE dt = '${bizdate}' GROUP BY user_id HAVING COUNT(*) > 1
)
SELECT COUNT(*) AS dup_user_cnt,
       SUM(c) AS dup_record_cnt
FROM dup;
-- 阈值:dup_user_cnt < 100 AND dup_record_cnt / total < 0.001

教训:P0规则一定要做「容忍度设计」。绝对值=0这种阈值往往不切实际,必须留出业务上能接受的微小偏差空间,否则一定会因为一些非数据问题(CDC、时区、消息乱序)导致全链路停摆。

总结

有效的DQC规则=五类校验维度(空值/唯一/值域/参照/勾稽)+ 动态基线 + 三级响应(P0/P1/P2)。规则不是越多越好,而是要精准、有容忍度、可配置。面试时这样答:先讲五类规则模板,再讲基线策略和分级响应,最后用一个真实的踩坑故事收尾,绝对加分。

下一篇聊一个面试常考的选型题:Great Expectations、Deequ、DataWorks DQC,三大主流DQC工具怎么选?我都用过,给你一个真实的选型框架。

你们生产环境的DQC规则大概有多少条?P0级别的有多少?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

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