5 багов, которые стоили миллионы долларов
Код может быть опасен. Одна ошибка способна уничтожить компанию, убить людей или обрушить половину интернета. Вот пять историй, которые должен знать каждый разработчик.
Почему баги так дороги?
Мы привыкли думать о багах как о мелких неприятностях: кнопка не работает, страница не загружается, приложение вылетает. Перезагрузил — и всё ок. Но в мире высоких технологий, финансов и критических систем одна строчка кода может стоить миллиарды.
Цена ошибки растёт экспоненциально
Баг, найденный на этапе написания кода, стоит $1. На этапе тестирования — $10. В продакшене — $100. После того, как он вызвал инцидент — $1000+. А если он уничтожил компанию...
Эти истории — не страшилки для запугивания джунов. Это реальные события, которые изменили индустрию и заставили пересмотреть подходы к разработке, тестированию и развёртыванию.
Давайте разберём каждый случай детально: что произошло, почему это случилось, и какие уроки мы можем извлечь.
1 Knight Capital Group
$440 000 000 за 45 минут
Дата: 1 августа 2012 года
Место: Нью-Йоркская фондовая биржа (NYSE)
Итог: Банкротство одной из крупнейших торговых фирм США
Предыстория
Knight Capital была одной из крупнейших торговых фирм на Wall Street. Они обрабатывали около 17% всех торгов акциями на американских биржах. Их торговые алгоритмы совершали миллионы сделок в день.
1 августа 2012 года NYSE запустила новую программу — Retail Liquidity Program (RLP). Knight Capital готовила обновление своего торгового ПО для поддержки этой программы.
Что пошло не так?
За несколько дней до запуска инженеры Knight Capital начали разворачивать новый код на 8 серверах. Но процесс развёртывания был ручным, и на одном из серверов новый код не был установлен.
Проблема усугублялась тем, что новый код переиспользовал старый флаг SMARS. Этот флаг раньше активировал функцию "Power Peg" — старую логику для тестирования, которая:
- Покупала акции по рыночной цене (максимальной)
- Мгновенно продавала их по рыночной цене (минимальной)
- Не имела никаких ограничений на количество сделок
// Псевдокод того, что произошло
if (SMARS_FLAG_ENABLED) {
// На 7 серверах: новая логика RLP
executeRetailLiquidityProgram();
} else {
// На 1 сервере: СТАРАЯ логика Power Peg
// которая покупала дорого и продавала дёшево
// в бесконечном цикле!
executePowerPeg(); // КАТАСТРОФА
}
Хронология катастрофы
9:30 — Открытие торгов
Восьмой сервер активировал старый код Power Peg
9:31 — Первые странные сделки
Система начала покупать и продавать акции 154 компаний
9:32 — Рынок реагирует
Цены некоторых акций подскочили на 10%+
9:35 — Knight замечает проблему
Но не понимает, в чём дело
9:45 — Панические попытки остановить
Инженеры отключают серверы один за другим, но не тот!
10:15 — Наконец остановлено
45 минут, 4 миллиона сделок, $440 миллионов убытков
За 45 минут система совершила около 4 миллионов сделок по 154 акциям на сумму $7 миллиардов. Компания потеряла $10 миллионов в секунду.
Последствия
Knight Capital потеряла больше, чем заработала за всю свою историю. Компания была вынуждена принять экстренное финансирование, а затем была продана конкуренту Getco за копейки.
Уроки Knight Capital
- Автоматизируйте развёртывание — ручной деплой на 8 серверов = человеческая ошибка
- Не переиспользуйте старые флаги — удаляйте мёртвый код
- Имейте kill switch — возможность мгновенно остановить всё
- Мониторинг аномалий — $10М/сек убытков должны были вызвать алерт
- Feature flags с таймаутом — старый код не должен жить вечно
2 Cloudflare Outage 2019
27 минут, весь мир
Дата: 2 июля 2019 года
Причина: Одна регулярка (regex)
Итог: Половина интернета недоступна 27 минут
Предыстория
Cloudflare — одна из крупнейших CDN-компаний в мире. Через их сеть проходит около 10% всего интернет-трафика. Они защищают миллионы сайтов от DDoS-атак с помощью WAF (Web Application Firewall).
WAF использует правила на основе регулярных выражений для обнаружения и блокировки вредоносных запросов. В тот день инженер добавил новое правило...
Что произошло?
Инженер добавил правило для обнаружения XSS-атак:
(?:(?:\"|'|\]|\}|\\|\d|(?:nan|infinity|true|false|null|undefined|symbol|math)|\`|\-|\+)+[)]*;?((?:\s|-|~|!|{}|\|\||\+)*.*(?:.*=.*)))
Выглядит как обычный regex, верно? Но в нём скрывалась смертельная ловушка — catastrophic backtracking.
Что такое catastrophic backtracking?
Движок регулярных выражений работает методом перебора. Когда паттерн не совпадает, он "откатывается" и пробует другой вариант. Для некоторых regex количество вариантов растёт экспоненциально:
Проблемный паттерн: .*.*=.*
Этот паттерн говорит: "любые символы, потом ещё любые символы, потом знак равенства, потом ещё любые символы"
| Длина строки | Количество вариантов | Время |
|---|---|---|
| 10 символов | ~1 000 | <1 мс |
| 20 символов | ~1 000 000 | ~100 мс |
| 30 символов | ~1 000 000 000 | ~минуты |
| 40+ символов | Астрономическое | Бесконечность |
Когда этот regex встретил определённые HTTP-запросы, CPU на всех серверах Cloudflare по всему миру ушёл в 100% и завис.
Масштаб катастрофы
Поскольку Cloudflare проксирует трафик для миллионов сайтов, всё это стало недоступно:
Discord
Shopify
Medium
И сотни тысяч других сайтов. Интернет буквально "лёг" на 27 минут.
Уроки Cloudflare
- Тестируйте regex на больших входных данных — не только "счастливые" случаи
- Используйте инструменты анализа — есть regex-анализаторы для поиска backtracking
- Ограничивайте время выполнения — timeout для regex
- Canary releases — сначала на 1% серверов, потом на все
- Быстрый rollback — возможность откатить изменения за секунды
После инцидента Cloudflare добавила защиту от catastrophic backtracking во все regex-правила WAF и перешла на движок с гарантированным линейным временем выполнения.
3 AWS S3 Outage 2017
$150+ млн убытков рынка
Дата: 28 февраля 2017 года
Причина: Опечатка в команде
Итог: Треть интернета недоступна почти 5 часов
Предыстория
Amazon S3 (Simple Storage Service) — это хранилище файлов, на котором работает огромная часть интернета. Изображения, видео, бэкапы, статические сайты — всё это часто лежит на S3. Регион US-EAST-1 (Северная Вирджиния) — самый большой и старый регион AWS.
В тот день команда S3 выполняла рутинную операцию: удаление небольшого количества серверов, чтобы отладить проблему с биллинговой системой.
Что произошло?
Инженер выполнил команду для удаления нескольких серверов. Но из-за опечатки в параметре была удалена гораздо большая группа серверов, чем планировалось.
# Что планировалось (условно):
$ remove-servers --count=5 --subsystem=billing
# Что было введено (условно):
$ remove-servers --count=??? --subsystem=index
# Удалена критическая подсистема индексации!
Среди удалённых серверов оказались серверы подсистемы индексации. Эта система отвечает за то, чтобы S3 знал, где физически хранится каждый файл. Без неё S3 не мог найти ни один объект.
Эффект домино
S3 — это фундамент для множества других сервисов AWS. Когда он упал:
- AWS Console — иконки и статические файлы лежат на S3
- Lambda — код функций хранится на S3
- EC2 — AMI образы на S3
- И далее по цепочке...
Ирония: даже AWS Status Dashboard не мог показать статус сбоя, потому что сам работал на S3!
Пострадавшие сервисы
Slack
Trello
Quora
IFTTT
Docker Hub
Business Insider
Почему восстановление заняло так долго?
Подсистема индексации не перезапускалась много лет. За это время S3 вырос настолько, что полная инициализация индекса заняла несколько часов. Никто не тестировал восстановление в таком масштабе.
Уроки AWS S3
- Опасные команды должны требовать подтверждения — "Are you sure?"
- Ограничивайте blast radius — нельзя удалить слишком много за раз
- Регулярно тестируйте восстановление — disaster recovery drills
- Не кладите все яйца в одну корзину — мультирегиональность
- Status page не должен зависеть от основной инфраструктуры
После инцидента AWS добавила защиту от случайного удаления критических систем и ограничила скорость удаления серверов.
4 Therac-25
6 смертей, много травм
Период: 1985-1987 годы
Причина: Race condition в коде
Итог: 6 смертей, серьёзные лучевые травмы
Предыстория
Therac-25 — это медицинский аппарат лучевой терапии, созданный компанией AECL для лечения рака. Он мог работать в двух режимах:
- Electron mode — низкоэнергетический пучок электронов (для поверхностных опухолей)
- X-ray mode — высокоэнергетический пучок (для глубоких опухолей), который проходит через металлический рассеиватель
В X-ray режиме энергия пучка в 100 раз выше, и без рассеивателя она смертельна.
Что пошло не так?
Предыдущие модели (Therac-6 и Therac-20) имели аппаратные блокировки: физические датчики не позволяли включить высокую энергию без рассеивателя. Но в Therac-25 эти блокировки заменили программными.
В коде была race condition: если оператор очень быстро менял параметры (за 8 секунд или меньше), система не успевала установить рассеиватель, но уже включала высокоэнергетический режим.
// Упрощённая иллюстрация проблемы
Thread 1: setBeamEnergy(HIGH); // t=0 Thread 2: checkSpreader(); // t=1 (ещё не установлен!) Thread 1: fireBeam(); // t=2 (смертельная доза) Thread 2: installSpreader(); // t=3 (слишком поздно!)
Трагедии
С 1985 по 1987 год произошло минимум 6 случаев передозировки:
- Пациенты получали дозы в 100 раз выше нормы
- Они описывали ощущение как "электрический удар" или "огонь"
- Трое погибли в течение недель от лучевой болезни
- Ещё трое умерли позже от осложнений
- Многие получили тяжёлые ожоги и инвалидность
Почему проблему не нашли раньше?
- Невоспроизводимость — баг проявлялся только при определённой скорости ввода
- Доверие к ПО — производитель утверждал, что "это невозможно"
- Плохие сообщения об ошибках — аппарат показывал "MALFUNCTION 54" без объяснения
- Переиспользование кода — код от Therac-6/20 использовался без учёта отсутствия аппаратной защиты
Уроки Therac-25
- Race conditions убивают — буквально
- Нельзя заменять аппаратную защиту программной для критических систем
- Тестируйте edge cases — "быстрый оператор" никто не проверял
- Понятные сообщения об ошибках — "MALFUNCTION 54" бесполезно
- Defense in depth — несколько уровней защиты
Therac-25 — это каноническая история в курсах по software engineering. Она показывает, что код может убивать, и почему тестирование критических систем требует особого подхода.
5 Ariane 5 Flight 501
$370 млн за 37 секунд
Дата: 4 июня 1996 года
Причина: Integer overflow
Итог: Взрыв ракеты с 4 спутниками на борту
Предыстория
Ariane 5 — флагманская европейская ракета-носитель, разработанная Европейским космическим агентством (ESA). На разработку ушло 10 лет и около $7 миллиардов. Первый запуск должен был продемонстрировать возможности новой ракеты.
На борту находились 4 научных спутника Cluster стоимостью $370 миллионов для изучения магнитосферы Земли.
37 секунд полёта
T+0 — Старт
Ракета успешно стартует
T+36.7 с — Сбой навигации
Инерциальная система выдаёт ошибочные данные
T+37 с — Резкий поворот
Автопилот пытается "исправить" несуществующее отклонение
T+39 с — Самоуничтожение
Система безопасности взрывает ракету
Техническая причина
Код инерциальной навигационной системы (SRI — Inertial Reference System) был взят из успешно работавшей Ariane 4. Но Ariane 5 была мощнее и быстрее.
В коде была переменная для хранения горизонтальной скорости. Она имела тип 16-битное знаковое целое число (максимум 32 767).
// Код на Ada (упрощённо)
horizontal_bias : INTEGER_16; -- Максимум 32,767 -- Ariane 4: скорость никогда не превышала 32,767 -- Ariane 5: скорость достигла ~40,000 horizontal_bias := Float_To_Integer(velocity); -- BOOM! Integer overflow → исключение
Когда значение превысило 32 767, произошло арифметическое переполнение. Код выбросил исключение (exception). Но обработчик исключений... просто остановил работу системы.
Есть и резервная навигационная система! Но она работала на том же коде. Обе системы отказали одновременно.
Автопилот получил диагностические данные вместо координат, интерпретировал их как "огромное отклонение от курса" и повернул ракету на 90°. Аэродинамические нагрузки начали разрушать конструкцию, и система самоуничтожения сработала.
Почему код не проверили?
- Код успешно работал 10 лет на Ariane 4
- Функция, где был баг, вообще не нужна во время полёта (только при подготовке)
- Её не отключили, потому что "а вдруг пригодится"
- Тестирование проводилось с симуляцией Ariane 4, а не Ariane 5
Уроки Ariane 5
- Не копируйте код слепо — новые условия могут сломать старые предположения
- Проверяйте диапазоны всех чисел — что если значение вырастет вдвое?
- Резервная система не должна иметь общего кода с основной
- Удаляйте мёртвый код — функция калибровки не нужна в полёте
- Тестируйте в условиях реальной эксплуатации
Общие уроки из всех историй
Удивительно, как часто повторяются одни и те же ошибки. Вот что объединяет эти катастрофы:
Тестирование
Тестировать нужно не только "happy path", но и edge cases: большие значения, быстрый ввод, параллельные операции.
Развёртывание
Автоматизация, canary releases, мониторинг. Ручной деплой = человеческие ошибки.
Kill Switch
Всегда имейте возможность быстро всё остановить. Секунды имеют значение.
Code Review
Свежий взгляд находит ошибки, которые автор не видит. 4 глаза лучше, чем 2.
Мёртвый код
Удаляйте неиспользуемый код. Он не "просто лежит" — он ждёт момента, чтобы сломаться.
Defense in Depth
Несколько уровней защиты. Если один сломается, другие должны удержать.
Заключение
Эти истории — не повод бояться программировать. Они — напоминание о том, какую ответственность мы несём. Код, который мы пишем, влияет на реальный мир: деньги, здоровье, жизни.
Каждая из этих катастроф привела к улучшениям в индустрии. Knight Capital изменила подходы к автоматизации торговли. Cloudflare улучшила защиту regex. AWS добавила safeguards. Therac-25 стала обязательным кейсом в курсах software engineering.
Учитесь на чужих ошибках — это гораздо дешевле, чем совершать свои.
"Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?"
— Brian Kernighan