2012 年 8 月 1 日早上,美国股市开盘才几分钟,骑士资本的一台下单路由服务器就在往市场发它根本不该发的单子,一发就是几百万笔,公司里还没人知道为什么。系统一边照常交易,工程师一边查。美国证券交易委员会后来查明,骑士没有任何规程告诉员工出了大问题该怎么办,只能主要靠技术团队在实盘里边找边修。
他们试过的办法之一,是把当天上线的新代码,从装对了的那七台服务器上撤下来。证交会的原话是:「这一步让问题更严重了。」原本只在第八台上跑的那段出错的旧代码,这下在另外七台上也跑了起来。
从表面看,这一步似乎合理:新代码上线当天出了新毛病,那就先撤新代码。他们看不见的,是各个零件之间怎么连着。新代码借用了一个开关,这个开关从前用来启动一个叫 Power Peg 的旧功能。骑士 2003 年就不用它了,却一直没删。2005 年,Power Peg 里负责数「已经成交多少股」的那部分被挪到了别处,旧代码从此不知道什么时候该停。偏偏有一台服务器漏装了更新。骑士系统里另有一处知道这些单子早已成交,消息却始终没传到路由器那里。
而系统的速度比人想事情快得多。大约 45 分钟里,骑士成交了 3.97 亿股,攒下的仓位最后让它亏了 4.6 亿美元。其余经过见案例。
两个旋钮
耶鲁大学社会学家查尔斯·佩罗,研究的是大型组织和它们的技术怎样酿成灾难。按出版社对他《正常事故》一书的概括,他从两个维度给高风险系统分类:「复杂交互还是线性交互,紧耦合还是松耦合」。
交互复杂,是说各部分能以没人计划过、也没人容易看清的方式互相影响。紧耦合,是说它们之间几乎没有余量:没有缓冲,没有停顿,来不及让人插手。佩罗在 2011 年把两者放在一起讲:在有些系统里,「多个故障出人意料地相互作用,可能造成一场任何设计者都想不到、任何操作员都看不懂的事故」。而「如果系统同时又是紧耦合的,任何干预都挡不住一连串故障把它拖垮」。
他把这类事故叫作「正常」,并非因为它常见,而是「因为这些特性就长在系统里」。多装警报、多加备份,并不能把它们去掉。他写道:「哪怕是小故障,它们之间非线性的、意想不到的相互作用,也能击穿这些安全系统。」
很少见,也不是借口
佩罗一再强调,真正的正常事故很少见。在同一篇 2011 年的文章里,他列了一串灾难,说只有一起算数:三里岛。1979 年 3 月 28 日凌晨 4 点左右,这座核电站的一个泄压阀卡在了打开的位置。据美国核管理委员会说,控制室的仪表却告诉值班人员,阀门是关着的。操作员担心系统的一部分会被水完全灌满,就调小了应急冷却水。堆芯过热,部分熔化。和骑士的工程师一样,他们是照着眼前看到的画面在做事。
佩罗写道,有灾难潜力的系统更常见的出事原因,是「监管不力、警告被无视、生产压力、削减成本、培训不足,诸如此类」。
这些寻常的毛病,骑士一样不少。部署没有第二个人核对。开盘前,97 封指出问题的自动邮件没人理会。所以这个理论不是给马虎开脱的。它的用处是一个设计上的提问:这个系统里,哪些部分可能以我们没画出来的方式互相影响?哪里根本没有时间拦住一个错误?佩罗的答案是减少前者、放松后者,比如把一个紧紧咬合的整体,改造成一个个模块。
耦合也决定了怎么恢复。2024 年的 CrowdStrike 故障里,出错的文件由一个在 Windows 启动早期就加载的安全驱动读取。按 CrowdStrike 当时的公告,第一步是重启电脑,让它有机会下载修好的文件;要是重启后又崩了,公告接着建议进入安全模式或 Windows 恢复环境,删掉那个文件。
小网店
设想一家小网店,为了省事把三样工具连在一起:网站接单,一张表格记库存,一个脚本在任何货品库存低于十件时自动向供应商补货。一天晚上,店主给新产品线加了一列。脚本是按列的位置读库存的,这下它可能读成了价格,以为每样东西都快卖光了,趁大家睡觉下了一大笔订单。改成按列名读取,就去掉了那个藏着的牵连;给单笔订单设上限,或者加一道早上人工确认,就多出了缓冲。
会连锁放大的智能体
Anthropic 讲自己怎样搭多智能体研究系统时,两个旋钮都碰到了。它发现,「对主智能体的小改动,会不可预料地改变子智能体的行为」,而且「微小的改动会连锁放大成行为上的大变化」。它写道,智能体要长时间运行、带着状态,所以「错误会累积」,「没有有效的缓解措施,系统的小故障对智能体可能是灾难性的」。
Anthropic 没有用佩罗的术语,但它的几项办法,加的正是他说的那种缓冲。智能体出错后可以从出错的地方接着跑,配上重试逻辑和定期检查点,不用从头再来。新版本用「彩虹部署」上线,新旧两版同时运行,流量逐步切过去,免得一次更新弄坏正干到一半的智能体。
骑士的工程师走了看似合理的一步,可系统跑得比他们看懂它还快。要让这样的系统宽容一点,得趁它开门营业之前。