四月十七日凌晨一点十二分,查询服务器正面的硬盘灯由断断续续的绿,变成了一条几乎不熄的红线。
正式上线还不到十个小时。
半地下室已经关了灯,托管机房却亮得没有昼夜之分。马飞站在机柜前,隔着带孔的铁门看那盏红灯,耳边是风扇持续的尖鸣。值班记录本摊在折叠桌上,最后一行写着:零点五十八分,平均返回时间四点一秒,拒绝率百分之三点四。
一点零五分以后,记录空了。
不是系统恢复了,而是值班的兼职学生忙着接电话,再没有手抄数字的空隙。
“页面还能开。”机房管理员说,“就是慢一点。红灯不代表坏了。”
马飞没有回答。他在终端上输入一条查询,回车后,屏幕停在空白页。十五秒过去,浏览器下方仍显示正在连接。
第二次查询在十一秒后返回,十条结果只显示了标题,摘要全部缺失。第三次直接收到“服务器忙”。
机器没有关,用户却已经失去了服务。
苏雨桐在一点二十六分赶到。她从出租车下来时外套扣错了一颗,进门后没有解释,只把四十三封问题邮件的分类表放到折叠桌上。
出租车费十二元,超过她平时一周的公交钱。她把票据压在值班本下,注明上线故障交通,提醒马飞天亮后别忘了报销。机房里没有热水,只剩管理员半杯冷茶,她喝了一口便皱眉放下。
“又多了二十七封。”她说,“有人连续刷新,有人以为自己的拨号网络断了,还有三个人说,同一个词每次得到的结果数量不一样。”
马飞把终端让给她。监控程序每五分钟才记录一次总查询数,看不见单个请求在什么环节堵住。两人能确认的只有三件事:进程还在,硬盘队列很长,可用内存正在下降。
“先停抓取。”苏雨桐说。
抓取程序与索引构建虽然已经放在旧机器上,但新服务器仍要定时接收增量文件。零点四十分开始的一批增量索引,比平时大了近一倍。复制、校验和查询同时争抢磁盘,像三队人挤进一扇门。
值班学生拔掉连接旧机器的网线。
红灯没有暗下来。
“不是它一个。”马飞看着进程表,“热门查询的结果没有缓存。今天白天大家搜得太散,看不出来;晚上有个论坛贴了我们的网址,几百个人都在搜‘星辰搜索’和‘中关村公司名录’。同一批文档列表被反复从盘上读。”
“那就把这两个词写进缓存。”兼职学生说。
“写进去,下一篇帖子换两个词,我们再半夜赶来写?”苏雨桐问。
学生不说话了。
马飞把墙上的发布指标抄到纸上。公开前,他们证明了八十并发下系统可以稳定返回,前提是查询分布来自十万次回放样本。真实上线以后,用户不是均匀到来,热点也不会提前递交申请。
他记得未来成熟搜索系统会怎样分层缓存、拆分索引、调度请求,也记得更昂贵的机器、更快的网络和多年积累的运维工具。但那些名词不能替眼前这台服务器多出一块内存,也不能让一个未经验证的改动自动安全。
“先降级。”他说。
“降什么?”
“摘要、相似页面提示、结果数量精确统计。保留标题、来源地址和前十条结果。超过六秒还没完成的查询,不再一直排队,直接告诉用户系统繁忙。”
机房管理员皱眉:“刚上线就少功能,别人会说你们不行。”
“现在他们等十五秒也看不到功能。”
“可在线率报表会很难看。”
马飞转过头:“什么报表?”
“机器开机时间。你们租机柜时不是说,要做连续在线服务吗?”
马飞看着那盏红灯,忽然明白一个危险的诱惑。只要不杀进程,不把页面彻底关掉,机器就可以在记录里保持“在线”。至于用户拿到的是空白、残缺结果还是错误提示,可以不进那张表。
这种数字最容易漂亮,也最容易把问题藏到事故发生以后。
“从今天起不用机器开没开做唯一指标。”他说,“记三项:用户请求有没有在六秒内得到完整结果,是否明确收到降级或拒绝,系统有没有返回无法解释的内容。空白页算失败,不算在线。”
苏雨桐已经拉开椅子坐下。她把三项指标写在故障本新的一页上,又加了第四项:同一查询在同一索引版本下是否一致。
“还要显示版本。”她说,“否则用户说两次结果不同,我们连他落在哪一版索引上都不知道。”
两人没有直接改主程序。他们先复制一份当前配置,在备用端口启动降级模式,用二十条常见查询、十条长题名和五条乱码样本重跑。标题和地址能够返回;不生成摘要后,单次磁盘读取明显减少。连续发出一百次相同热门查询,响应仍慢,但队列不再继续增长。
凌晨两点零九分,降级开关正式启用。
网页顶端出现一行灰字:当前访问量较大,系统已暂时关闭摘要与结果统计;自然排序规则不变。
这句话是苏雨桐写的。马飞原本只想写“系统维护中”,她把那五个字删掉了。
“我们没有维护。”她说,“我们是在容量不足时减少功能。原因不同,用户下一次判断要不要信我们,也会不同。”
两点十七分,平均返回时间下降到五点三秒。两点四十二分,降到三点八秒。硬盘灯仍频繁亮起,却终于出现了肉眼可见的间隔。
兼职学生松了口气:“那我把故障结束时间写两点四十二?”
“不。”马飞说,“这只是止血。故障从什么时候结束,要等我们把摘要重新打开,还能维持指标以后再写。”
他让学生回去睡觉,自己和苏雨桐留下。两人从服务器日志、机房流量记录和邮件时间里,一分钟一分钟拼出零点四十分后的变化。日志缺失的地方一律标成“不知道”,不拿平均值填补。
兼职学生不肯先走,直到马飞在离岗时间上签字才收拾书包。他本想坐首班公交,管理员从抽屉里翻出一张夜班折叠床,让他先睡两个小时。苏雨桐把那张出租车票移到报销页,又给学生补了一份夜班餐,虽然机房里根本没有东西可吃。
清晨五点,窗外还没有完全亮,原因链终于写在白板上:论坛链接带来集中查询;热点词重复读盘;增量索引同时复制;请求没有超时退出;队列持续堆积;内存被等待请求占满;摘要生成进一步放大磁盘压力。
没有哪一项单独足以压垮服务器。它们在同一个小时碰到一起,才让红灯变成一条线。
马飞盯着那串箭头,想起另一条在火焰中闭合的链:氧泵、分离时序、热防护。前世事故后,太多人先寻找一个可以命名、可以更换、可以问责的坏零件。可真正杀死他的,从来不是一个零件。
苏雨桐察觉他握着笔不动,问:“想到什么?”
“想到机器没坏,不代表系统没坏。”
“这不是今晚刚学的吗?”
“以前也学过。”马飞把笔放下,“代价更大。”
她没有追问,只把白板上的最后一个箭头改粗。
上午八点,陈维明打来电话。他昨晚把星辰搜索的网址发给了几个做通信设备的客户,其中一个正是论坛帖子的转发者。
“热闹是好事。”他说,“我看了,网站一直能打开。你们对外完全可以说上线首夜零停机。”
马飞看着故障本:“不能这么说。”
“为什么?进程死了?”
“没有。”
“机房断网了?”
“也没有。”
“那就是零停机。”
“一点零五到两点四十二,大量用户无法在合理时间得到完整结果。我们还关闭了摘要和统计。机器没有停,服务失败了。”
电话另一头沉默几秒。陈维明显然不喜欢这个答案,却没有像过去那样立刻争论控制权。他问:“写进给客户的材料,会不会太难看?”
“可以不拿故障做宣传,但不能把它改名叫成功。”
“那你准备怎么说?”
“上线首夜出现容量故障,降级后恢复基本查询。原因正在复核,改进完成前不承诺峰值能力。”
陈维明叹了口气:“你这人做生意,有时候像给自己拆台。”
“如果客户按假的容量买了服务,下一次才是真的拆台。”
通话结束后,苏雨桐把“零停机”三个字写在便签上,贴到红灯旁边,又在前面画了一道斜线。
上午十点,他们逐步打开摘要功能,每次只放行四分之一请求,并继续观察队列。十一点十三分,全部功能恢复。连续一小时内,百分之九十五的查询在四点二秒内返回,超时请求得到明确提示,同一索引版本的结果保持一致。
马飞这才在故障本上写下结束时间。
持续十小时三十三分,其中严重影响一小时三十七分。
处置不是“重启服务器”,而是五条具体措施:增量索引只在低峰传输;索引发布与查询磁盘分离排队;热门查询缓存必须根据日志自动淘汰;所有请求设置时间上限;降级状态向用户明示。
最后一条是:建立每小时用户失败率,不以进程存活代替服务可用。
午后,新的访问高峰到来。硬盘灯又亮得密集,却没有连成红线。
马飞在值班表上加了夜班,先把自己的名字填进第一格,又在故障本封面写下日期。苏雨桐把外套那颗扣错的纽扣重新系好,拿走报销联,准备回学校上下午的课。
五金店老板托人送来一只铝饭盒,里面是白菜和两只馒头。饭盒在路上漏了点汤,故障本右下角湿了一小块。马飞先把本子挪开,拿纸擦净桌面,再把“严重影响一小时三十七分”誊到封面。
下午两点,下一轮访问高峰过去。硬盘灯仍不停闪,间隔却能数得出来。马飞吃完冷馒头,把夜班联系电话抄在机房墙上,红灯旁那张划掉“零停机”的便签没有揭。