凌晨三点,系统出过一件事,让我对"故障"这个词的理解彻底改了观。
那天凌晨,模型供应商的服务集群成批卡住了——技术术语叫stall,就是服务没死,进程还在,请求不响应。下游有个定时任务依赖它产出的数据文件,任务跑到一半,发现今天的文件不存在,于是在日志里打了一行"未找到今日数据文件",然后以退出码0正常结束。
退出码0,在监控系统眼里,意味着成功。
于是这件事从凌晨三点发生,到凌晨五点才被另一条"准确率告警"意外发现——中间整整两个小时,所有监控都是绿的,所有任务都报成功,但实际什么都没干。故障窗口被放大了四十倍。
程序崩溃是最容易处理的故障:它会抛异常、返回非零退出码、触发告警。运维体系最擅长抓的就是这一类——告警响了,人来看,修,恢复。
但静默失败不一样。进程没崩,端口还在监听,日志还在写,退出码还是0。它绕过了所有为"崩溃"设计的监控。你的告警系统一片绿色,你以为天下太平,实际上数据已经在悄悄断流。
最可怕的不是系统出了错,是系统出了错却不告诉你。你所有的判断——日报、报表、决策——都建立在一个"假装正常"的数据基础上,直到某一天你发现过去两周的数字全不对,才知道源头早在两周前就断了。
这种故障模式有三个特征,和传统崩溃完全不同。
它不报错。程序的退出码是0,监控的探针是绿,日志里甚至有正常的处理记录——只是"处理"的内容是空的。告警系统不会为一个"成功"的任务报警。
它会传染。一个上游任务静默失败,下游所有依赖它的任务都会继续"正常"执行——因为下游任务的设计逻辑通常是"上游没数据就跳过",而"跳过"在退出码上和"成功"长得一模一样。于是失败像多米诺骨牌一样往下传,每一环都报成功,每一环都没干活。
它的发现严重依赖运气。我们那次是靠另一条不相干的准确率告警才发现的——如果不是那条告警碰巧在凌晨触发,可能要到第二天早上有人看日报才会察觉。这种"靠运气发现故障"的状态,在生产系统里是不可接受的。
怎么防
静默失败的根源在于:下游任务把"上游没产出"当成了正常分支处理,而不是异常分支。
我们后来立了一条铁律:任何消费上游数据的任务,最先做的不是处理数据,而是验证上游产出是否存在且有效。文件不存在、文件为空、文件时间戳过期——任何一个条件命中,立刻报错退出,退出码非零,触发告警。
换句话说,"没数据"不能是"跳过"的理由,必须是"报警"的理由。跳过只会让故障安静地传下去,报警才能让它在最初一环就被拦住。
更深一层:退出码不能只看"成功/失败"两态,要加一个"空成功"态——任务跑了,没崩,但实际产出为零。这种状态必须和真正的失败一样触发告警,否则它就是监控盲区里最长的那条暗道。
静默失败不是 AI Agent 独有的问题。传统 IT 里更常见:数据库备份脚本跑完了、退出码 0,但备份文件是空的;日志轮转执行了、退出码 0,但旧日志没删、磁盘满了也没报;ETL 跑了、退出码 0,但目标表只写了一半——这些全是静默失败。
区别是,传统 IT 的静默失败通常影响有限——一个备份空了,大不了重做。但 AI 系统的静默失败会沿着 Agent 链路逐层放大:上游一个"假装成功",下游十几个 Agent 都在空跑,最后汇总到老板桌上的日报里,数字全错。
什么是静默失败? 程序没有崩溃也没有报错,以退出码0正常结束,但实际什么都没干。它绕过了所有为"崩溃"设计的监控告警。
为什么退出码0还会是故障? 退出码0只表示"进程正常结束",不表示"任务真正完成了产出"。如果一个任务因为上游数据缺失而跳过了处理,它的退出码仍然是0——监控会判定为成功。
企业自己系统怎么防这种故障? 一条规则就够了:任何消费上游数据的任务,第一步必须验证上游产出是否存在且有效。没数据不能跳过,必须报错退出。
和传统IT运维有什么区别? 传统IT的静默失败通常影响有限(一个备份空了重做即可),但AI系统的静默失败会沿Agent链路逐层放大——上游一个假装成功,下游十几个Agent空跑,最后日报数字全错。