DataOps 真不是“把数据跑起来”:真正难的是出了问题,你能不能找到锅在哪
DataOps 真不是“把数据跑起来”:真正难的是出了问题,你能不能找到锅在哪
大家好,我是 Echo_Wish。
很多公司刚开始搞 DataOps 的时候,目标都很朴素:
“把每天的数据任务自动跑起来。”
于是开始上调度平台、写 ETL、接 Kafka、搞 Airflow、配监控,再整几个告警机器人。
看起来挺现代。
但真正运行几个月以后,问题往往来了:
- 昨天报表里的数据为什么少了 20%?
- 这个指标到底是谁算出来的?
- 为什么凌晨 2 点开始数据就不对了?
- 是源库数据变了,还是 ETL 脚本改了?
- 是某个任务失败了,还是上游虽然成功但数据已经“坏了”?
- 产品问:“这个数字你敢保证吗?”
这时候很多团队才发现:
DataOps 最难的从来不是“让数据流动起来”,而是让数据变得可信、可追踪、可恢复。
所以今天不聊特别玄乎的概念,就站在一个运维人的角度,聊聊 DataOps 真正落地时,我认为最值得做好的三件事:
数据质量、流水线、可追溯性。
而且我一直有个比较“土”的判断:
没有质量检测的数据流水线,只是一个跑得很快的数据污染器。
一、第一关:数据质量,不是最后看报表才发现错了
传统的数据处理流程经常是这样的:
MySQL
↓
ETL
↓
ODS
↓
DWD
↓
DWS
↓
BI报表
看起来非常漂亮。
但是如果源头突然出现:
订单数量:10000
变成:
订单数量:100
整个链路可能依然:
任务成功
SQL成功
数据库成功
报表成功
最终:
所有系统都是绿色,业务数据却已经凉了。
这就是很多 DataOps 系统最容易踩的坑:
技术层面的成功 ≠ 数据层面的成功。
所以数据流水线里面必须加入 Data Quality。
二、数据质量至少要检查这几个东西
我自己比较推荐把数据质量检查简单拆成 5 类。
1. 完整性
比如订单表:
SELECT COUNT(*)
FROM orders
WHERE order_id IS NULL;
如果结果:
0
说明暂时正常。
如果:
1523
那就别继续跑了。
因为后面所有数据都可能建立在一堆“无主订单”上。
2. 唯一性
比如订单号应该唯一:
SELECT
order_id,
COUNT(*) AS cnt
FROM orders
GROUP BY order_id
HAVING COUNT(*) > 1;
正常情况:
0 rows
如果出现:
ORD20260901001 2
ORD20260901008 5
那就说明数据出现重复。
这种问题如果不拦住,到了统计层面可能直接变成:
“今天销售额怎么突然翻倍了?”
三、范围校验也非常重要
例如订单金额:
SELECT *
FROM orders
WHERE amount < 0
OR amount > 1000000;
你可能觉得:
“金额大一点有什么问题?”
问题是:
假设正常订单金额:
10 ~ 5000
突然出现:
999999999
这很可能不是一个超级大客户。
而是:
amount = price * quantity
某个地方类型转换或者数据异常了。
所以我一直认为:
数据质量规则,本质上就是给业务数据加了一层“防火墙”。
四、不要只检查“有没有数据”,还要检查“数据有没有变得奇怪”
这个特别重要。
例如每天订单量:
周一:10231
周二:10532
周三:10982
周四:10123
周五:10321
突然:
周六:231
SQL 可能完全成功。
数据库也没有报错。
但是人一眼就能看出来:
不对劲。
所以可以做同比、环比或者基线检测。
例如:
today = 231
yesterday = 10321
change_rate = (today - yesterday) / yesterday
if abs(change_rate) > 0.5:
raise Exception("订单量异常波动")
这其实已经开始从传统 ETL 走向真正的 DataOps 了。
因为我们不再只是问:
“任务跑成功了吗?”
而是在问:
“这次产生的数据合理吗?”
五、第二关:流水线不要只是“能跑”,而要做到“失败可控”
很多团队的 Pipeline 长这样:
任务A
↓
任务B
↓
任务C
↓
任务D
↓
任务E
其中任务 C 失败。
然后呢?
不知道。
人工去服务器:
ps
tail -f xxx.log
grep ERROR xxx.log
然后开始:
“谁昨天改了代码?”
这其实已经不是 DataOps 了。
这是:
半自动人工救火系统。
六、一个真正靠谱的 Data Pipeline,至少应该有状态
例如:
class JobStatus:
PENDING = "PENDING"
RUNNING = "RUNNING"
SUCCESS = "SUCCESS"
FAILED = "FAILED"
RETRYING = "RETRYING"
任务执行的时候:
def run_job(job):
update_status(job.id, "RUNNING")
try:
extract()
transform()
load()
update_status(job.id, "SUCCESS")
except Exception as e:
update_status(job.id, "FAILED")
send_alert(job, e)
raise
看起来很简单。
但这里面有一个非常重要的思想:
任何任务都必须知道自己现在处于什么状态。
这样调度平台才能真正做到:
PENDING
↓
RUNNING
↓
SUCCESS
或者:
RUNNING
↓
FAILED
↓
RETRYING
↓
SUCCESS
七、重试不是越多越好
这是很多 DataOps 新手特别容易犯的错误。
看到失败:
for i in range(10):
try:
run()
break
except:
time.sleep(10)
觉得:
“多重试几次,总能成功。”
其实不一定。
如果 SQL 写错了:
SELECT xxx
FROM abc
你重试 100 次也没有用。
如果上游数据库临时网络抖了一下:
第一次失败
第二次成功
重试才有价值。
所以应该区分:
临时性故障
↓
可以 Retry
和:
业务/代码错误
↓
直接 Fail
例如:
RETRYABLE_ERRORS = (
TimeoutError,
ConnectionError
)
try:
run_job()
except RETRYABLE_ERRORS:
retry()
except Exception:
fail()
这就是运维思维进入 DataOps 的地方。
八、第三关:可追溯性,往往比“监控”更重要
这是我认为 DataOps 最容易被低估的一部分。
假设业务人员问:
“报表里面这个 128932 到底怎么算出来的?”
你回答:
“SQL 算出来的。”
这句话其实等于没回答。
真正应该能够回答:
128932
↓
DWS.sales_amount
↓
DWD.order_detail
↓
ODS.order
↓
MySQL订单库
↓
订单 ORD20260901001
这就是:
Data Lineage,数据血缘。
九、为什么数据血缘这么重要?
因为数据出了问题以后,我们最想知道的不是:
“现在错了。”
而是:
“从哪里开始错的?”
例如:
源数据库
↓
ODS
↓
DWD
↓
DWS
↓
BI
如果 BI 出现异常,我们应该能够反向追踪:
BI指标异常
↓
DWS异常
↓
DWD正常
↓
ODS异常
↓
源系统异常
那么问题就非常清楚:
不是 BI 的锅。
这就是可追溯性真正的价值。
十、最简单的办法:给每一次数据处理建立 Trace ID
这个思路其实和微服务链路追踪非常像。
例如:
import uuid
trace_id = str(uuid.uuid4())
context = {
"trace_id": trace_id,
"job_name": "order_daily",
"start_time": datetime.now()
}
后续所有日志都带上:
logger.info(
"start transform",
extra={"trace_id": trace_id}
)
数据库也记录:
trace_id
job_name
source_table
target_table
start_time
end_time
status
row_count
error_message
这样以后查问题的时候:
SELECT *
FROM data_job_log
WHERE trace_id = 'xxx';
就可以把一次完整的数据处理过程串起来。
十一、再进一步:记录数据版本
如果数据非常重要,我甚至建议记录:
数据版本
代码版本
配置版本
Schema版本
任务版本
例如:
{
"trace_id": "202609010001",
"job": "order_daily",
"git_commit": "8f31a2c",
"schema_version": "v12",
"config_version": "prod-20260901",
"source_version": "2026-09-01",
"row_count": 102331
}
这时候如果有人问:
“为什么昨天的销售额和今天重新计算的不一样?”
你至少有机会回答:
昨天:
代码 8f31a2c
Schema v11
今天:
代码 9a72bc1
Schema v12
然后继续追。
否则只能:
“应该是哪里改了吧……”
这句话在生产环境里,杀伤力很大。
十二、把三件事情真正串起来
我比较推荐的一套 DataOps 思路,可以画成这样:
┌──────────────┐
│ 数据源 │
└──────┬───────┘
↓
┌──────────────┐
│ Data Quality │
└──────┬───────┘
↓
┌──────────────┐
│ Pipeline │
└──────┬───────┘
↓
┌──────────────┐
│ 数据处理 │
└──────┬───────┘
↓
┌──────────────┐
│ Quality Gate │
└──────┬───────┘
↓
┌──────────────┐
│ 数据仓库 │
└──────┬───────┘
↓
┌──────────────┐
│ BI / AI │
└──────────────┘
旁边再挂一套:
日志
↓
Trace ID
↓
数据血缘
↓
指标监控
↓
告警
↓
审计
这时候才算比较完整的 DataOps。
十三、我特别推荐一个原则:数据质量要“左移”
很多公司都是:
数据产生
↓
数据加工
↓
数据入库
↓
BI报表
↓
用户发现异常
↓
开发排查
这其实太晚了。
更好的方式是:
数据进入
↓
立即校验
↓
异常拦截
↓
告警
↓
人工/自动处理
↓
继续流水线
也就是说:
不要让脏数据一路跑到报表层才被发现。
这跟 DevOps 里的:
代码提交
↓
CI
↓
测试
↓
安全扫描
↓
部署
是一个道理。
DataOps 本质上也是:
把质量控制融入流水线,而不是等事故发生以后再人工排查。
十四、最终可以形成一个 DataOps 生产闭环
一个比较成熟的数据平台,我认为至少应该形成:
数据产生
↓
质量检查
↓
流水线调度
↓
数据加工
↓
质量检查
↓
数据发布
↓
指标监控
↓
异常告警
↓
问题定位
↓
血缘追踪
↓
版本回溯
↓
修复
↓
重新处理
注意最后两个字:
重新处理。
这其实非常关键。
如果今天数据错了,我们不是简单地:
DELETE
INSERT
然后祈祷。
而应该能够根据:
Trace ID
+
数据版本
+
代码版本
+
任务版本
+
血缘关系
重新构建数据。
这才是真正意义上的:
可恢复的数据系统。
写在最后
我越来越觉得,DataOps 这个东西没有想象中那么“高大上”。
说到底就是解决三个非常接地气的问题:
第一:数据到底对不对?
靠:
完整性
唯一性
一致性
范围校验
异常波动
第二:任务到底有没有正常跑?
靠:
Pipeline
状态管理
Retry
超时
依赖
告警
第三:出了问题到底是谁的锅?
靠:
Trace ID
日志
数据血缘
版本
审计
所以如果让我用一句话总结 DataOps,我会说:
DataOps 不是把数据流水线做得多复杂,而是让数据从“产生”到“消费”的每一步,都能够被验证、被观察、被追踪、被恢复。
尤其是现在 AI、RAG、实时数仓越来越普及以后,数据质量的重要性只会越来越高。
模型可以暂时不够聪明。
但如果你喂进去的数据本身就是错的——
那模型再聪明,也只能非常认真地胡说八道。
这可能才是 DataOps 最值得我们认真思考的地方。
我是 Echo_Wish。
如果说 DevOps 解决的是“代码怎么稳定地交付”,那么 DataOps 真正要解决的,其实是:
数据怎么稳定地流动,并且让所有人敢相信它。
- 点赞
- 收藏
- 关注作者
评论(0)