Knight Capital: как $440 млн испарились за 45 минут из-за одного деплоя
1 августа 2012 года, 9:30 утра по Нью-Йорку. Открывается рынок. В этот момент один из крупнейших маркет-мейкеров США — Knight Capital Group — начинает терять деньги со скоростью примерно $10 млн в минуту. Через 45 минут компания, обрабатывавшая до 17% объёма торгов на NYSE и NASDAQ, оказывается на грани банкротства с убытком около $440 млн.
Самое важное: это была не ошибка трейдера и не баг разработчика. Это был провал релиз-процесса. Каждое отдельное звено выглядело безобидно — а вместе они стоили компании существования. Разберём как QA: где должны были сработать барьеры и почему не сработали.
Что произошло за 45 минут
В тот день NYSE запускала новую программу Retail Liquidity Program (RLP) — механизм для розничных ордеров. Knight подготовил новый код в своей системе маршрутизации ордеров SMARS (Smart Market Access Routing System), чтобы поддержать RLP.
Код выкатывали вручную на 8 серверов. На 7 из 8 новый код встал корректно. На восьмой сервер код не попал — и никакой автоматической проверки, что выкатка прошла на всех нодах, не было.
В новом коде переиспользовали старый, давно не используемый флаг. Раньше этот флаг включал внутреннюю функцию под названием Power Peg — тестовый механизм 2003 года, который намеренно двигал цену акции вверх, чтобы проверять поведение других компонентов. Функцию давно вывели из эксплуатации. Хуже того: ещё в 2005 году ту часть логики, которая останавливала Power Peg после исполнения родительского ордера (счётчик кумулятивного объёма), перенесли в другое место — а сам код Power Peg оставили лежать в системе как мёртвый.
Когда в 9:30 рынок открылся и RLP-флаг был выставлен:
- 7 обновлённых серверов — корректно запускали новый RLP-код.
- 8-й сервер (старый код) — тот же самый флаг разбудил спящие 8 лет
Power Peg. А поскольку предохранитель-счётчик из этого кода был удалён ещё в 2005-м, Power Peg начал слать в рынок дочерние ордера без остановки — покупал дорого, продавал дёшево, и не понимал, что родительский ордер давно исполнен.
За ~45 минут Knight исполнил около 4 млн сделок по 154 акциям — порядка 397 млн акций, набрав чистую длинную позицию примерно на $3,5 млрд и короткую на $3,15 млрд. Каждая сделка была чуть-чуть убыточной. Умножьте на миллионы.
Как инженеры сделали хуже
Дальше — момент, который должен впечатать в память каждого, кто катит релизы под давлением.
Ещё до открытия рынка система SMARS разослала 97 автоматических email-алертов, упоминавших ошибку про Power Peg. Их никто не воспринял как критичные: у алертов не было severity, владельца и эскалации — обычный шум в почте.
Когда деньги уже потекли, команда решила, что проблема в новом коде, и начала его откатывать. Но откатывали так: удалили новый код с тех самых 7 правильных серверов. В результате теперь уже все 8 серверов крутили старый код с разбуженным Power Peg — и кровотечение усилилось. Это классическая ошибка: «катить вперёд» / откатывать вслепую под паникой, без отрепетированного runbook.
Почему это провал процесса, а не «плохой программист»
Соблазн назвать виновного разработчика велик. Но разбор показывает цепочку из 6–7 независимых отказов, и почти все они — про процесс, а не про код:
- Переиспользовали feature-флаг. Одно и то же значение флага означало разные вещи в старой и новой версии кода. Это бомба замедленного действия.
- Не удалили мёртвый код. Power Peg лежал в системе 8 лет после вывода из эксплуатации — с уже снятым предохранителем.
- Ручной деплой без верификации. Выкатка на 8 серверов руками, без автоматической проверки, что код встал на всех.
- Нет parity-чека флота. Никто не сверил, что все 8 нод идентичны по версии перед стартом торгов.
- Алерты-шум. 97 предупреждений ушли в пустоту — без severity, маршрутизации и блокировки старта.
- Нет kill switch. Не было кнопки, которая по бизнес-аномалии (взрывной объём ордеров) сама бы остановила поток. 45 минут — это вечность.
- Откат не отрепетирован. Под стрессом сделали хуже, потому что не было проверенной процедуры отката.
Ни одно из этих звеньев само по себе не «фатальный баг». Фатальной стала их комбинация — и отсутствие барьеров, которые должны ловить именно комбинации.
7 уроков для QA и релиз-инженерии
1. Никогда не переиспользуй флаги
Старый флаг — ретайрь и удаляй, под новую фичу заводи новый ключ. Значение флага не должно означать разные вещи в разных версиях. Полезно почитать про гигиену тогглов у Мартина Фаулера: тоггл — это код с жизненным циклом, его нужно удалять, а не консервировать.
2. Мёртвый код — это пассив, а не «просто лежит»
Особенно опасен мёртвый код с отключёнными предохранителями. Если функция выведена из эксплуатации — удаляй её целиком, а не «на всякий случай оставим».
3. Деплой должен быть автоматическим и верифицируемым
Машина не «забудет» 8-й сервер. После выкатки — автоматическая проверка, что нужная версия встала на всех нодах. Никаких «вроде раскатил руками».
4. Parity-чек перед стартом
Перед открытием торгов / релизом — автоматическая сверка версий и конфигов по всему флоту. Любое расхождение блокирует старт.
5. Канареечный rollout вместо big-bang
Выкатывать критичный код большим взрывом в день запуска новой биржевой программы — максимальный риск. Канарейка на части трафика + наблюдение + автопрокатка дальше.
6. Алерты с severity, владельцем и эскалацией
97 писем «в молоко» — это антипаттерн. Критичные pre-flight алерты должны блокировать старт, а не теряться в почте. У каждого алерта — владелец и понятный путь эскалации.
7. Kill switch и мониторинг бизнес-аномалий
Поток ордеров, PnL, объём — это бизнес-метрики, по которым нужен автоматический circuit breaker: при выходе за порог система сама режет поток, не дожидаясь человека. И отдельно — отрепетированный откат с runbook, чтобы под паникой не делать хуже.
Чек-лист релиз-процесса (10 пунктов)
- Флаги не переиспользуются: старый ретайрнут, новая фича — новый ключ.
- Мёртвый код удаляется, а не консервируется (особенно с снятыми предохранителями).
- Деплой автоматизирован; есть автопроверка, что версия встала на ВСЕХ нодах.
- Parity-чек версий/конфигов по всему флоту перед стартом.
- Канареечный / поэтапный rollout, не big-bang в день высокого риска.
- У алертов есть severity, владелец и эскалация; критичные pre-flight алерты блокируют старт.
- Kill switch отключает аномальный поток на бизнес-уровне (объём ордеров, PnL).
- Откат отрепетирован и описан в runbook; «катить вперёд» вслепую запрещено.
- Мониторинг бизнес-аномалий с авто-халтом по порогу, а не только техметрики.
- Деплой-скрипты и feature-флаги проходят code review как обычный код.
Чем закончилось
Акции Knight рухнули примерно на 75% за два дня. Компанию спас экстренный вход инвесторов на $400 млн; уже в декабре 2012-го она согласилась на слияние с Getco — так появился KCG. В 2013 году SEC оштрафовала Knight на $12 млн за нарушение Market Access Rule (правило 15c3-5): у фирмы не было адекватных письменных процедур контроля за деплоем и доступом к рынку.
$440 млн за 45 минут — это цена того, что выглядело как «мелкие детали процесса»: переиспользованный флаг, незамеченный сервер, неудалённый код, проигнорированные алерты. Ровно те вещи, которые ловит зрелый QA- и релиз-процесс.
Источники
- Doug Seven — Knightmare: A DevOps Cautionary Tale
- Wikipedia — Knight Capital Group
- Martin Fowler — Feature Toggles (гигиена флагов)
- Первоисточник по фактам и хронологии — приказ SEC, Release No. 34-70694 (16.10.2013).