八台服务器,七台装了新代码。第八台上,一段 2003 年就停用、2005 年被改坏、此后再没人测过的代码,正在等一个开关。开市铃响,开关合上了。
先看结果,因为结果决定了你读下面每一段时的心跳。
出问题的系统叫 SMARS——Knight 的自动化股票订单路由器 [S1 §1]。它的工作是接收上游传来的「母单」,再按市场流动性拆成若干「子单」发到各交易场所去成交 [S1 §12]。
作为参照:2011 到 2012 年间,Knight 的总交易量大约占全美上市股票交易的 10%,仅 SMARS 一家就占 1% 以上 [S1 §11]。这不是一个边缘系统。
2012 年 7 月,Knight 为了配合纽交所一个新项目(零售流动性计划,RLP,原定 8 月 1 日启动)改造系统,其中包括在 SMARS 里部署新代码 [S1 §12]。
新代码要替换掉订单路由器里一段没在用的旧代码——一个叫 Power Peg 的功能 [S1 §13]。它的履历是这样的:
Knight 停用 Power Peg 功能 [S1 §14]。但代码没有删——它仍然留在服务器上,并且仍然可以被调用 [S1 §13]。
Knight 把 Power Peg 里那个累计数量函数挪到了 SMARS 代码序列中更靠前的位置 [S1 §14]。
这个函数原本的职责是:统计母单已成交多少股,在母单成交完毕后让代码停止继续拆发子单 [S1 §14]。
它就是刹车。而刹车被搬走了。
搬完之后,Knight 再没有测试过 Power Peg 代码,因此不知道它若被调用还能否正确工作 [S1 §14]。
新的 RLP 代码复用了那个原本用来激活 Power Peg 的开关(flag)。计划是把 Power Peg 代码删掉,这样开关置 "yes" 时激活的就是新功能 [S1 §13]。
计划的前半句执行了,后半句没有。
从 2012 年 7 月 27 日起,新代码分批部署,连续几天陆续放到 SMARS 的服务器上 [S1 §15]。
部署过程中,一名技术人员漏了 8 台服务器中的 1 台 [S1 §15]。
接下来是三句话,每一句都是一道本该存在的防线:
8 月 1 日早晨,开市前。SMARS 处理了一些盘前订单,Knight 内部一个系统开始自动生成邮件——称作 BNET rejects——内容引用 SMARS,并标明一个错误:"Power Peg disabled" [S1 §19]。
从约 8:01 a.m. ET 起,到 9:30 a.m. 开市之前,系统一共发出了 97 封这样的邮件给一组 Knight 人员 [S1 §19]。
但是:Knight 并没有把这类邮件设计成系统告警,相关人员一般也不看它们 [S1 §19]。
SEC 在文件里写下了这一段里最重的一句话——它是实时发出的、正是由部署失误引起的、本可提供在开市前发现并修复问题的机会;然而这些通知在开市前无人处理,开市后也没有被用来诊断问题 [S1 §19]。
开市。带着那个被复用开关的订单一到第八台服务器,就触发了残留的 Power Peg 代码 [S1 §16]。七台装了新代码的服务器处理正确 [S1 §16]。
因为累计数量函数——那个刹车——早在 2005 年就被搬走了,这台服务器对每一笔进来的母单,不管已经成交了多少股,都持续快速地发出子单 [S1 §16]。
系统的另一部分其实已经知道母单成交完毕了。但这个信息没有传给 SMARS [S1 §16]。
212 笔母单,变成了数百万笔子单 [S1 §17]。
B。这封邮件里有两个极其罕见的信号叠在一起:它指名了一个本该不存在的功能(Power Peg 早在 2003 年就停用了 [S1 §14]),并且它在部署刚结束的窗口里出现。任何一条单独出现都可能是噪音,两条同时出现指向的只有一个方向——某台机器上的代码和你以为的不一样。而验证"八台版本是否一致"是一条只需要几分钟、且能在开市前完成的确定性检查。
A 是当天实际发生的事的近似,而且它的推理在绝大多数日子里都是对的:盘前噪音确实多。它错在把"我通常可以忽略这类邮件" 当成了 "这封邮件可以被忽略"。区别在于内容——「Power Peg disabled」不是一个通用错误,它是一个指名道姓的、不该存在的东西。
C 是所有选项里最糟的,因为它把唯一的信号源关掉了。SEC 特别指出这些消息「是实时发出的、正是由部署失误引起的」[S1 §19]。降噪的正确做法是先搞清楚它在说什么再决定要不要静音;反过来做,你就永久失去了这次机会。第 14 章会展开:告警治理的第一步是分类,不是过滤。
D 比 A 强,但它把一个 deadline 明确 的问题变成了一个没有 deadline 的问题。开市时间是 9:30,这是一个硬边界;"转发问一句"没有绑定这个边界,同事可能在忙、可能在路上。当一个问题有硬 deadline 时,正确的动作是给它配一个在 deadline 之前必须完成的确定性检查,而不是发起一次异步对话。
A。这是全书反复出现的那条原则在最高标价下的重演:先止血,再治病(第 5 章教训壹)。此刻每一秒都在产生不可撤销的成交,而"查清原因"是一个时长未知的任务。切断连接的代价是可计算的(当天这部分业务停摆),不切断的代价是无上界的。SEC 的认定里明确列出 Knight 没有在系统自身行为异常时叫停 SMARS 的规程 [S1 §21]——也就是说,当天这个选项在流程上并不存在。
B 是真实团队走的路,也是工程师本能上最"负责任"的一条。SEC 记录:Knight 主要靠技术团队在实盘交易环境里边跑边查,期间系统持续发出数百万笔子单 [S1 §27];技术人员排障期间,Knight 一直连着市场并继续发单 [S1 §10]。它错在一个隐含假设:诊断时间是有限的。当损失以秒计息时,这个假设不成立。
C 方向正确,但当天这条路是断的。那个账户(文件中称 33 号账户)确实有一个 200 万美元的总头寸限额——但这个限额没有连到任何自动控制 [S1 §23]。更糟的是它混装多种来源的头寸,人员无法快速判断膨胀的头寸是什么、从哪来 [S1 §24]。一个不能阻止下单的限额,只是一个数字。
D 是本页最重要的一个选项,请务必读完。「回滚」是所有人的肌肉记忆,当天 Knight 也这么做了:他们把已正确部署的那七台服务器上的新 RLP 代码卸载了。结果是——让情况更糟:更多进来的母单开始在那七台上也激活 Power Peg 代码,重演第八台上已经发生的事 [S1 §27]。
为什么回滚会放大故障?因为故障的根源不是"新代码有 bug",而是新旧代码共用同一个开关。卸载新代码,等于把剩下七台也交还给亡灵。当你不知道根因时,回滚不是安全操作——它只是另一个未经测试的变更。
B。这道题的答案由 SEC 亲自写在了文件里。Knight 当年的应对正是 A——三项精准修复,一项不多 [S1 §33]。而 SEC 的评语是:Knight 没有广泛地考虑自己是否有足够的控制来防止错误订单进入市场,无论发单的是哪个系统、无论那个系统出错的具体原因是什么 [S1 §33]。十个月后,另一个系统、另一个原因,同样的错误订单,标价 4.6 亿美元。
A 不是错的——它是不够的,而这正是它危险的地方。三项修复都合理、都被执行了、都关闭了工单。事故复盘最常见的失败模式就是这样:把一次事故当成一个 bug 来修,而不是当成一次抽样来读。正确的问法不是"怎么防止这件事再发生",而是"这件事说明我们缺了哪一类控制"。
C 把系统问题归给了人。培训与清单能降低概率,但改变不了"系统可以无限制发单"这个结构性事实。关卡二已经演示过同一个陷阱:把安全寄托在人的仔细上,等于承认你没有护栏。
D 的算术是对的,风险判断是错的。750 万相对公司体量确实可控——但这次损失真正的信息量不在金额,在于它证明了"错误订单可以自由流向市场"这条通路是通的。同一条通路第二次被走通时,金额由市场决定,不由你决定。
SEC 的认定里有一句话,几乎可以当作本关卡的标题。它指出 Knight 缺乏足够的技术治理控制与监督规程,既不足以确保新代码的有序部署,也不足以阻止那些「已不打算再使用、却仍留在可访问市场的服务器上」的代码被激活;同时也缺乏引导员工应对重大技术与合规事件的控制与规程 [S1 §9.D]。
三个分句,正好是上面三条教训。
此刻的「秒抢」:约 1000 万用户、230 人,第 12 章刚交完那份秒杀作战方案。作战方案里每一条限流、降级、预扣库存,都是一个开关。开票日当天,这些开关会被大量拨动——而这正是 Knight 那天早晨的处境。
| Knight 的失效点 | 「秒抢」的对应物 | 本周动作 | 关联 |
|---|---|---|---|
| 八台里漏了一台 | 灰度发布、分批重启、多可用区部署 | 发布流程加一道版本一致性断言:所有实例上报的构建哈希不一致就阻断发布并告警 | 本关 教训壹 |
| 复用旧 feature flag | 开票开关、限流开关、降级开关、AB 实验位 | 建开关台账:每个开关登记创建时间、负责人、预计下线时间;超期未下线的开关定期清理。禁止复用已下线开关的键名 | 第 12 章 |
| 97 封没人看的邮件 | 各种「记录但不告警」的 ERROR 日志 | 把日志分三档:可忽略 / 需查看 / 必须响应。第三档必须走值班通道,不能只发邮件 | 第 14 章 |
| 没有叫停系统的规程 | 下单链路、预扣库存、异步落库 | 为每条关键链路准备一个演练过的杀开关,并在预案表里写明:谁能按、按下后业务影响是什么、恢复步骤是什么 | 第 12 · 16 章 |
| 限额没连到自动控制 | 库存扣减上限、单用户下单频率、支付金额阈值 | 逐个检查:这个阈值是能阻止请求,还是只能事后看见?只能看见的一律标记为待改造 | 第 10 · 11 章 |
最后一个练习,请今天就做:列出你系统里所有还开着的 feature flag,逐个问「它上一次被翻动是什么时候」和「如果现在有人把它打开,会发生什么」。
凡是回答不出第二个问题的开关,都是 Power Peg。
逐句核对清单见构建仓库 sources/FACTS-level3.md;来源原文留档见 sources/raw/sec-34-70694-knight-capital.txt。关于本次事故的第三方技术推测未被采信为事实依据。