OOOMETA/OS
← 返回洞察

2026 年 9 月 · 5 分钟阅读

逾期报警响了 9136 次,没人知道该修哪一条

逾期报警响了 9136 次,没人知道该修哪一条

关键定义

可行动报警 把要做的事以哪一条、归谁、超期多久、下一步是三者之一的形式交付出来的报警;只报数量的输出不算报警。

输出契约 一段自动化代码承诺交付的内容形态。它的价值上限由这个形态决定,而不是由检出率决定。

公司里有一道自动巡检,每十五分钟扫一遍待办池,把它认为已经过了截止时间的条目挑出来。从六月上线到现在,它跑了 9136 次,每一次的输出都是同一句话的变体:扫过哪些文件、发现 14 个逾期项。它没有一次说出这 14 个是谁。

这条报警一直在响,也一直没用。读到「14 个逾期」的人没有办法知道该处理哪一条、归在谁名下、超期多久,于是它每十五分钟刷一次、刷了一整天,池子里没有任何一条被推动。问题不在检出的能力,而在输出的路径:明细列表在代码里是存在的,藏在一个需要显式开启的详细模式下面,而调用它的定时任务从来没有打开过这个开关。

九千多次运行,日志里没有一条名字

巡检的机制很朴素。它先把「已过截止时间」的条目算成一个集合,再按开关决定输出哪种形式:开关关着,输出集合的大小;打开,输出集合的内容。开关的默认值是关,调用方是一个定时任务,参数里没有它。于是每次运行,明细都在内存里被算出来,又在写日志之前被丢掉,只留下一个数字。

这类缺陷很难被察觉,因为它在所有常规健康信号上都是正常的。任务按时执行,退出码是零,输出非空,毕竟有一个 14 在那里,运行频率也稳定。只看有没有报警、报得及不及时,永远看不出问题。被丢掉的那个东西不在数字里,它在数字背后。

发现它的方式也很普通:有人要处理其中一条逾期项,去问了一句还有哪些也是逾期的,然后发现没有任何一条记录能回答这个问题。问题于是从这条逾期怎么办,变成了我们为什么只能看到一个数字。这类缺陷通常不是被监控发现的,是被一个具体的人的一个具体问题顶出来的。

我们把它打开做了一次实地确认。同一次运行下,十四条逾期全部有了名字,其中有几条一眼就能看出为什么要紧:「风控位的刷新失败被静默吞掉,单个标的的坏数据能停掉整个组合的刷新」「决策执行卡上的价位对自动监控结构性不可见,已经连续六个执行窗口落空」。剩下的九条是合规自证、字段与事实相反、上下文被静默截断这一类,共同点是全部已经确过诊。

为什么只报数字等于没报

一条报警要被处理,读者需要三样东西:名字,也就是到底哪一条;归属,也就是谁负责;还有一个可判定的下一步。缺了名字,处理动作就退化成去把池子翻一遍,而翻一遍的代价,恰好是报警本来要省下的那份代价。成本没有消失,只是从机器转移到了人身上,并且每十五分钟转移一次。

计数还有一种更隐蔽的性质:恒定的数字看起来像稳态。14 这个数每天出现,读的人很快就学会把它当背景,就像机房里的风扇声。等到它变成 25,也只是从背景音变成更响的背景音,而不是一个事件。

这里没有人偷懒。巡检精确地完成了被交代的那件事:报告数量。输出契约写的是数量,它交付的就是数量,而且交付得无可挑剔。

这也解释了为什么「加一道巡检」这种修复方式经常不管用。巡检是一段代码,它的价值上限由输出契约决定,而不由检出率决定。检出率百分之百、输出不可行动的巡检,和一个不存在的巡检在实际效果上没有区别,代价却更高。

第二种自静音:只提醒,不阻断

同一套系统里还有第二处形状相同的问题,方向相反。待办池的容量提醒在代码里带着一句注释:只警告,永不阻断。也就是说,当天新增条目超过阈值时,系统会提醒,但不会拒绝任何一条进入队列。提醒被记录下来,队列照旧增长,三个月之后,提醒的次数和队列的长度一起变成了背景音。

「只警告不阻断」和「只报数字不报名单」看起来是两个问题:一个省略了名字,一个放弃了约束。机制却是同一条,都是把一个可执行的判定降级成信息,再把这信息交给不会自动消化它的地方。判定一旦降级成信息,状态上就等于不存在。

这也是我们这一轮把两处一起改的原因。报警和容量约束是同一条链路上的两个断面,只补其中一个,另一个会立刻把问题重新制造出来。

这里要划一条边界:并不是所有报警都必须带名字。磁盘将满、证书将过期这类单值阈值报警本身就是可行动的信息,因为它指向一件事,而不是一批事。名字之所以必要,出现在报警的对象是集合的时候——集合里每一条的代价不同、归属不同、下一步不同,把它们压成一个数字,丢掉的恰好是最贵的那部分信息。

十四条里有九条是关过三次的旧路径

打开明细之后,我们看到的第二件事比第一件严重。这些逾期项大多不是新发现的,而是已经确诊、已经建过卡、然后在静默里过期的。三个月里,同一类事,也就是待办池只报不消化,被处理并关闭过至少三次:六月两次,七月一次,九月两次。

每次关闭的动作都一样:人工挑一遍,剔掉重复项与临时项。机制本身没有变过。所以同一条路径在关闭之后继续复现,最新的读数是七天之内新开三十九张、关闭十六张,净增二十三张。

把这条路径的成本算出来并不难。巡检每十五分钟跑一次,一天九十六次,三个月九千一百三十六次。假设每次有一成的概率让人停下来看一眼,那就是九百多次注意力中断;而它在这三个月里推动的条目是零。这不是效率不够高的问题,是一条净值为负的链路在稳定运行。

还有一层结构值得单独说。发现问题的环节和消化问题的环节共用了同一个待办池:池子越满,新的结论越容易在里面排队排到过期。于是产出越多,入口越堵,越堵就越容易产生新的过期项,上面那十四条里,就有几条是负责诊断的那一方自己开的。没有人不努力;这个环是共用一个入口造出来的。

把这两件事放在一起看,就能看出这次修复的难度不在技术。加一个开关是一行代码的事。难的是承认那九千多次运行之所以没有产出结论,是因为我们把「报告」当成了「交付」。

我们改了三件事

第一件,报警带名字,但要防噪。明细不能每十五分钟打十四行字下去,那不是修复,只是换一种噪音。做法是只在集合发生变化时输出,或者只输出前几条加总数,完整清单落到当日归档里,需要的时候可以翻。

防噪这块有一个必须先回答的设计问题:变化的粒度取在哪。按确认状态的变化触发,一条被反复确认的条目会让报警抖动;只按成员增删触发又太安静,可能漏掉「同一条已经逾期三十天」这种质变。取哪一种不取决于偏好,取决于你的队列里哪种变化更贵。

第二件,逾期项必须有出口。每一项只有三种可接受的处置:做、降级、明确废弃。没有出口的队列不会收敛,只会积累,这正是上面那条复现路径的直接成因。

第三件,也是最关键的一件:把巡检的输出契约从报告数量改成交付可执行的事。前两件是它的实现方式。顺带一个关于默认值的教训,这个开关默认关着过了一整个季度,说明默认值本身就是产品决策,改默认值比新增开关重要得多。

改完之后怎么判断它真的好了?看下一条报警的输出能不能同时满足三件事:说出名字、只在集合变化时开口、并且每一条都带着三种处置中的一种。只看第一条不够。只加名字而不加出口,队列会继续积累,只不过这次积累的每一条都有人知道它存在。知道而没有出口,比不知道更让人难受,并不更好。

最后说一个组织上的观察。这类输出缺陷能活很久,是因为它不制造事故,只消耗注意力。它不会让任何一次值班出丑,也不会在复盘里留下名字;它只是让每个人多花几分钟去翻池子,然后按老习惯挑出自己熟悉的那几条处理掉。九千多次运行没有推动任何一条,恰好是这条链路上最安静的部分,而安静正是它没被发现的原因。

还要承认没做完的部分。这套改动让逾期项有了名字和出口,但没有解决容量问题:诊断的产出仍然进同一个入口。那是下一层的问题,代价更高,我们目前还没有答案。

三个可以照抄的判据

如果你的 agent 运维里也有自动巡检,可以拿三个问题过一遍。

  • 打开最近一条报警的输出,看它能不能直接回答要处理哪一条、归谁、超期多久。回答不了,这条报警就是噪音,无论它多准时、跑了多少次。
  • 打开队列,看逾期项有没有出口定义。只有提醒而没有三种处置的队列,一定会积累,而且积累的速度和产出的速度成正比。
  • 看默认值。明细默认关、阻断默认不生效、导出默认不动,默认值才是真实的产品行为,文档里写的能力不是。

报警的价值不在它检出了多少,而在它把多少变成了带着名字的待办。下一次你看到一条只报数字的报警,可以问一句:如果它今晚安静下来,会有人发现吗?如果不会,那它现在也没有在工作。

来源

常见问题

为什么只报数量的报警不算报警?+

读者无法把它变成动作:不知道处理哪一条、归谁、超期多久,只能自己去翻池子,代价等于报警本来要省的代价。

巡检按时运行、退出码为零,为什么还是没发现问题?+

因为被丢掉的是明细,而明细不在任何健康信号里。任务按时、输出非空、频率稳定,这些指标全部正常。

这次修复动了哪三处?+

报警带名字并只在集合变化时输出;逾期项必须有三选一出口,也就是做、降级、明确废弃;输出契约从报数量改成交付可执行的事。

为什么说默认值就是产品决策?+

明细开关默认关闭持续了整个季度,于是所有运行都只报数字。改默认值比新加一个开关更能改变实际行为。

这个问题解决了吗?+

逾期项有了名字和出口,但容量问题没有解决:诊断产出仍然进同一个待办池,那是下一层的机制问题。

我应该先检查什么?+

打开最近一条报警的输出,看它能不能回答哪一条、归谁、超期多久;不能,就先修输出,而不是先加巡检。