跳到主要内容
一个只在周五下午两点上班的 bug

一个只在周五下午两点上班的 bug

摸鱼办主任
摸鱼办主任

· 阅读约 4 分钟

你有没有见过一种故障,它有排班表。

每周五下午两点准时上线,每五十个请求里挑一个幸运儿回个 500,周一早上准点痊愈。

——比在座某些同事都自律。

(我先摆明立场:一个能按时上下班的 bug,比一个能按时上下班的同事难抓多了。)

【场景:周五下午两点零三分,工位上传来一声"又来了"】

团队第一反应当然是先查自己。慢查询?pg_stat_activity 翻了一遍,没有长查询、没有锁、没有阻塞事务,20 个连接齐刷刷躺在那儿,空闲。像极了考勤系统显示全员在岗,业务量就是上不去。

再查连接泄漏。新加的那个报告生成端点被翻来覆去审了一遍,try/finally 都在,预发压测也不漏。划掉。

再查外部定时任务。周五下午一点确实有个每周分析导出在跑——但它用的是独立数据库用户,而占着连接的那批,全是结账服务自己的账号。也划掉。

三条嫌疑全排除完,人还站在案发现场。这时候才咂摸出真正别扭的地方:

数据库说这些连接是空闲的,连接池说这些连接我占着。

两个系统各说各话,谁也不认错,跟月底甲方乙方对账一个德行。

打住,我差点开始讲连接池原理。这不是一篇讲连接池的文章,这是一篇讲人口普查的文章。

根因不在这一段排查里。它藏在"我们从没数过这台机器上到底有几个自己人"。

结账服务两个实例挂在负载均衡后面,每个池子上限 20,加起来 40,离 PostgreSQL 的 100 上限远得很。按纸面算,这个事故不该发生。

问题是纸面之外还站着一个。

几个月前做金丝雀部署实验,起了第三个实例,实验做完没人记得收尾。它不接线上流量,所以不出现在请求指标里,不出现在错误仪表盘里,不出现在任何一张你每天会打开的图上。

它在编,但不在岗。工资照发——只不过那份工资是 20 个数据库连接。

它启动的时候照样把池子填满,整整 20 个。更妙的是它的健康检查 ping 写错了,每隔几秒新建一个原始连接,不复用池里的,也不还,只等进程重启才清一次。

所以真实发生的事是这样:每周五下午自动扩缩容回收空闲实例,顺手把这个僵尸进程重启一回,20 根漏出去的线一次性归零。下周五再来一遍。

周一"自愈"不是因为它被治好了,是因为它下班了。

这不是故障,这是一张排班表。

(友情提示:周一早上自动恢复正常的系统,不一定是你周末修好的,也可能它只是双休。)

修起来便宜得离谱——下线那个实例。连接数从周五峰值的 94 掉到稳定 12,500 直接消失。

几个月定位,五分钟善后。这个比例在运维这行属于常规操作,不值得展开。

真正值钱的是后面那个补救:作者加了个每周任务,拿注册到负载均衡/服务发现里的实例列表,去比对实际接收流量的实例列表,差集里谁在空转超过几天,就往 Slack 里扔一条。上线之后,又逮到两个同类。

一核对编制,还有俩吃空饷的。

有人夸这篇最值钱的部分是保留了那些被排除的假设。我同意一半——它们值钱不是因为"展示了调试过程"这么体面,是因为连起来正好是一条指向案发的路线。慢查询、泄漏、定时任务,一条条排除掉之后剩下的那片空白,就是僵尸站的地方。

排查方向从"我的代码哪里错了"换成"这台机器上还有谁在连我的库",这一换,比手上任何工具都好使。

顺带一个我打算抄走的细节:事后看 pg_stat_activity,按 application_name 和 usename 分组,别只看总数。"一共有 94 个连接"这句话没信息量,"结账服务占了 74 个"才有。数人头和查工号是两件事。

Jennifer 在评论里说她中过同款——一个两年前为了演示搭的临时 staging worker,用的 Redis 连接池,还活着。临时工说好干三天,干满两年,这种配置我怀疑大多数公司都有。

(郑重声明:本文不针对任何具体的实例、进程或同事。它们已经下线了,愿它们安息,顺便把连接也还了 🫠)