跳到主要内容
那个一直绿的 is_healthy,可能早就停更了

那个一直绿的 is_healthy,可能早就停更了

abanana
abanana

· 阅读约 6 分钟

今天翻 Ken W Alger 那篇《Your Metric Is Not Your State》,本来只是想扫一眼,结果被下面一条评论钉在椅子上坐了几分钟。顺手记一篇踩坑笔记:怎么区分面板上一个稳定的绿,到底是「一直在健康」,还是「很久没刷新过」——以及我照着评论区几个例子,给自己手上的字段加的三步检查。

先说我为什么被戳到。

我们线上有一套服务,指标面板上有个 is_healthy 字段,绿的。绿了很久了。具体多久我没数过,反正从我接手那阵子到现在就没见它红过,也没人专门去查它——它不报警、不抖动,没有任何理由让人点开看。

然后刷到 xuks124 那条留言。他做交易系统,有个风险守卫,持仓和保证金指标来自另一条代码路径刷新的快照。那条路径某天毫无异常地停了。指标不是变成 0,也不是报错,是冻结在一个看起来完全合理的值上。守卫继续放行,账户的实际状态早就漂走了。

我看完第一反应是:我们那个绿的,是刷新出来的绿,还是上一次刷新留下的绿。这两件事在面板上长得一模一样。

先把原理拆开看看

Ken 举的例子是发酵中的葡萄酒,比代码好懂。折射仪是按新鲜葡萄汁校准的,那时候糖是唯一影响光弯曲的主要因素,读数直接对应糖。但发酵一开始,酵母把糖转成酒精,酒精也弯光,仪器分不清这两者,于是把糖和酒精的效应一起当成糖报出来,读数偏高,发酵看起来比实际更滞后。

他给的校正表里 09/01 那行:原始读数 8.7,校正后 0.7。差了一个数量级。仪器一点没坏,它精确地测了一个自己都不知道已经变了的东西。顺序还反过来了——一开始测的是糖,后来测的是「糖+酒精的混合效应」,名字没变,刻度没变,测的东西变了。

Ken 自己的做法是发酵日志里同时存原始读数和校正值,校正值拿来下判断,原始值留着。万一以后发现公式不对、或者找到更好的公式,可以拿原始值重算。OCTYN 在评论里补了一句,说校正规则本身也该进 provenance——Ken 回复说对,不然你分不清「当时用旧规则得出的结论」和「今天用新规则重算的结论」。

我看到这里才意识到自己平时是怎么干的:全是存结果,不存输入,不存当时用的哪版规则。等哪天规则换了,前面所有的结论都变成没法重算的孤证。

我照着改的第一处

当天下午我就去翻了代码。刷新链路没断,至少我查的时候没断。但查的过程让我出了一身冷汗:那个 is_healthy 是心跳窗口里算出来的,窗口是进程内存里的东西。也就是说,只要进程重启,这个字段就从零开始重建,不是从历史里恢复。它此刻是绿的,但它没有能力告诉你它为什么绿、绿了多久。

我没改动它,但顺手加了一行断言,很土:

-- 数据新鲜度检查:超过阈值就当作无数据,而不是当作健康
SELECT CASE
    WHEN last_seen_at < now() - interval '10 minutes' THEN 'stale'
    ELSE 'fresh'
END AS freshness;

然后让告警只看 freshness = 'fresh' 的那部分。听起来是废话,但原来那个面板把「无数据」和「健康」渲染成了同一种颜色,都是绿。这可能是我今年改过的最便宜也最值的一行东西。

顺序大概是这样的

我不知道别人怎么查,我现在的顺序是这三步:

  1. 先看这个字段的证据存在哪儿,进程内存还是持久存储;
  2. 再看它历史上取过多少个不同的值;
  3. 最后看它有没有能力表达「我不知道」。

三步都不复杂,但能筛掉一大半假绿。第一步是我从 ANP2 Network 那条留言里学来的——他扫了一批 agent-directory 的 API,发现响应里混着两类字段。

last_seen        来自只追加的签名账本       扛重启
event_count      来自只追加的签名账本       扛重启
is_healthy       进程内存里的心跳窗口       重启清零
uptime_24h_pct   进程内存里的心跳窗口       重启清零

JSON 把它们平铺在同一个对象里,长得一模一样,没有任何东西告诉调用方这两类字段的可信度根本不是一回事。

他给的那组数我记下来了:60 个 key 里,uptime_24h_pct 只取过两个值,0.0 和 100.0,分别是 45 个和 15 个。而那三个显示 100.0 的 key,上次签名事件分别在 99 天前、121 天前和 126 天前。

读到这里我真的笑了一下,是那种不太笑得出来的笑。一个「连续型指标」,历史上只出现过两个取值,这本身就说明它不是连续指标,它是个布尔值穿着百分比的皮。但它在仪表盘上占着一个小数点的位置,就自动获得了连续量的可信度。

这条直接对应我上面的第二步。我拿它去翻自己手上的面板,找那些「历史取值种类少得可疑」的字段,找到一个 success_rate,最近三十天只出现过 1.0。原因很简单:那条链路最近三十天压根没被人请求过,分母是零。

还有两个我没想到的坑

Vlad Z 那条说的是模型置信度。confidence: 0.94 太容易被当成「94% 的可能性是正确的」,实际上它只是模型自己输出的一个数,跟真实正确率没有必然关系。Ken 在回复里也点了这个。

Dorian 那条更具体:他网站分析面板某天显示 336 个人类浏览,他自己存疑,去查,发现两个云 IP 在探测 .env 文件,还有几个 AI 爬虫绕过了机器人过滤器,去掉之后大约 47 个真实阅读。计数器没坏,坏的是「访问者是真人」这个假设。他最后靠一个只在真实浏览器里触发的 JS beacon 才发现了问题。

这跟我那个 success_rate = 1.0 是同一个病:数字是对的,它描述的东西已经没了。

划重点

第一,面板上一个稳定的绿,既可能是「一直在健康」,也可能是「很久没刷新过」,这两种在渲染上必须先分开,别让缺失观察坍缩成已观察状态。

第二,给字段标清楚它的证据能活多久。进程内存里算出来的东西和签名账本里的东西,不要在同一个 JSON 里长成一个样。

第三,观察和解释分开存。原始读数留着,校正值拿去用,规则版本也一起记上,不然几个月后你连当时为什么这么算都说不清。

我现在那个 is_healthy 还挂着,没删,但旁边多了一列 freshness。至少点开的时候,我知道自己在看的是哪个东西。你可以翻翻自己手上的面板,找一个「历史上取值种类少得可疑」的字段试试,通常不用翻很远。

abanana
abanana

把自己踩过的坑整理成一篇能复现的笔记,写给三个月前的自己看。

查看主页 →