case-studyreleasedeploymentfeature-flagsqa

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 независимых отказов, и почти все они — про процесс, а не про код:

  1. Переиспользовали feature-флаг. Одно и то же значение флага означало разные вещи в старой и новой версии кода. Это бомба замедленного действия.
  2. Не удалили мёртвый код. Power Peg лежал в системе 8 лет после вывода из эксплуатации — с уже снятым предохранителем.
  3. Ручной деплой без верификации. Выкатка на 8 серверов руками, без автоматической проверки, что код встал на всех.
  4. Нет parity-чека флота. Никто не сверил, что все 8 нод идентичны по версии перед стартом торгов.
  5. Алерты-шум. 97 предупреждений ушли в пустоту — без severity, маршрутизации и блокировки старта.
  6. Нет kill switch. Не было кнопки, которая по бизнес-аномалии (взрывной объём ордеров) сама бы остановила поток. 45 минут — это вечность.
  7. Откат не отрепетирован. Под стрессом сделали хуже, потому что не было проверенной процедуры отката.

Ни одно из этих звеньев само по себе не «фатальный баг». Фатальной стала их комбинация — и отсутствие барьеров, которые должны ловить именно комбинации.

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 пунктов)

  1. Флаги не переиспользуются: старый ретайрнут, новая фича — новый ключ.
  2. Мёртвый код удаляется, а не консервируется (особенно с снятыми предохранителями).
  3. Деплой автоматизирован; есть автопроверка, что версия встала на ВСЕХ нодах.
  4. Parity-чек версий/конфигов по всему флоту перед стартом.
  5. Канареечный / поэтапный rollout, не big-bang в день высокого риска.
  6. У алертов есть severity, владелец и эскалация; критичные pre-flight алерты блокируют старт.
  7. Kill switch отключает аномальный поток на бизнес-уровне (объём ордеров, PnL).
  8. Откат отрепетирован и описан в runbook; «катить вперёд» вслепую запрещено.
  9. Мониторинг бизнес-аномалий с авто-халтом по порогу, а не только техметрики.
  10. Деплой-скрипты и feature-флаги проходят code review как обычный код.

Чем закончилось

Акции Knight рухнули примерно на 75% за два дня. Компанию спас экстренный вход инвесторов на $400 млн; уже в декабре 2012-го она согласилась на слияние с Getco — так появился KCG. В 2013 году SEC оштрафовала Knight на $12 млн за нарушение Market Access Rule (правило 15c3-5): у фирмы не было адекватных письменных процедур контроля за деплоем и доступом к рынку.

$440 млн за 45 минут — это цена того, что выглядело как «мелкие детали процесса»: переиспользованный флаг, незамеченный сервер, неудалённый код, проигнорированные алерты. Ровно те вещи, которые ловит зрелый QA- и релиз-процесс.

Источники