Как программные ошибки приводят к катастрофическим последствиям
Сегодня при обсуждении ПО следует учитывать ту высокую значимость, которую оно имеет в нынешних технологических решениях. Например, в мире аэрокосмонавтики ставки невероятно высоки, и программные сбои могут вести к катастрофическим последствиям.
В этой статье речь пойдёт о нескольких ярких случаях, когда сбои ПО серьёзно отразились на подобных критических средах, в которых ошибки недопустимы.
▍ ✈️ Кейс 1. Авария ракеты-носителя «Ариан-5»
Четвёртого июня 1996 года Европейское космическое агентство (European Space Agency, ESA) впервые запустило ракету «Ариан 5», отметив важный момент в истории исследования космоса. Однако миссия оказалась провальной, поскольку буквально одна строка кода привела к катастрофическому сбою и потере всего груза стоимостью почти полмиллиарда евро.
Ракета «Ариан 5» (фр. Ariane 5)
Эта ракета должна была доставить два спутника связи на геостационарную переходную орбиту. Запуск шёл гладко, пока ракета не отклонилась от траектории и не разрушилась на 37 секунде полёта. Было установлено, что причиной катастрофы стал программный сбой в системе наведения, которая отвечала за регулирование курса полёта ракеты. Сбой при запуске Ариан-5 был признан одним из самых дорогостоящих в истории ПО. Разрушение научных спутников привело к задержке исследований магнитосферы Земли почти на 4 года.
В чём же была причина? Мёртвая часть кода программы из последней миссии Ариан-4, которая стартовала примерно 10 годами ранее, содержала простую и вполне исправимую ошибку. Ракета определяла свою направленность движения вверх или вниз с помощью метода, известного как горизонтальное смещение, иногда также называемого «BH value». Его значение представлялось 64-битной переменной с плавающей запятой, которую система наведения использовала для преобразования в 16 16-битных знаковых целых. 64-битная переменная может представлять миллиарды значений, в то время как 16-битная может выражать лишь 65 535.
Нам известно, что в целых числах первый бит представляет знак, и что 16-битные целые могут находиться в диапазоне от -32 768 до 32 767. При этом числа с плавающей запятой создаются для отслеживания более широкого диапазона значений, используя то же количество битов между -1.8e+308 и -2.2e-308. Если вы попытаетесь сохранить такое значение в 16-битном целом числе, то оно значительно выйдет за его допустимые границы. По итогу в программном обеспечении ракеты произошло хорошо известное целочисленное переполнение.
Катастрофе поспособствовал и ещё один фактор, а именно требование операторов, чтобы Ариан-5 летела по значительно более крутой траектории в сравнении с предыдущими ракетами, что привело к невероятной высокой скорости вертикального движения.
Какие ключевые выводы можно сделать из этой катастрофы?
- Копирование кода без его понимания (карго-культ) представляет серьёзную проблему.
- Ещё одна проблема — это отсутствие подобающей обработки ошибок.
- Пренебрежение изменениями, внесёнными по требованию операторов, и
- Отсутствие необходимого тестирования.
В итоге комиссия по расследованию составила следующие рекомендации:
- R1 — избегать использования программ или систем, которые не являются обязательными. Во время полёта программное обеспечение не должно выполняться, если на то нет необходимости.
- R2, R10, R11 — критически необходимо проводить тестирование. Организуйте тестовую среду с максимальным объёмом тестового оборудования и проводите тщательное тестирование системы в закрытом цикле. Всем миссиям должны предшествовать полноценно реализованные симуляции с обширным покрытием тестами.
- R4 — проводить код-ревью. В любом языке программирования важны все детали кода.
- R6, R8 и R13 — необходимо повышать читаемость за счёт обработки исключений в задачах и создания резервных механизмов для поддержания стабильной работы во время сбоев. При определении критических компонентов важно также учитывать возможные программные сбои. Организуйте команду для выработки строгих правил тестирования ПО, обеспечивающих следование высококачественным стандартам.
- Ariane 5: Flight 501 failure — Report by the Inquiry Board (версия HTML).
- Ariane 5: A programming problem? Широкое обсуждение программного сбоя миссии Ариан-5.
▍ ✈️ Кейс 2. Как неперехваченное SQLException остановило работу авиалиний
В январе 2023 года агентство Reuters поведало об интересном инциденте, произошедшем в Федеральном управлении гражданской авиации (Federal Aviation Administration, FAA). В ходе анализа происшествия специалисты управления выяснили, что работающий по контракту специалист «непреднамеренно удалил файлы», вызвав общенациональный запрет на вылеты 11 января. Если говорить точнее, в тот день оказалось отменено более 11 000 полётов. Представители управления сообщили, что этот сотрудник работал над синхронизацией основной и резервной баз данных.
Это напоминает мне о проблеме, о которой рассказывается в главе 2 книги «Release It» Майкла Найгарда. Авиалинии планировали произвести аварийное переключение кластера базы данных, выступавшего основной системой, выстроенной по принципу сервис-ориентированной архитектуры. Этот поэтапный переход был запланирован и распределён по функциональности. Названная система обрабатывала поиск, возвращая для разных запросов (дата, время, город) детали перелётов.
Программное обеспечение авиалинии работало на кластере прикладных серверов J2EE с БД Oracle, обеспечивавшей резервную избыточность данных, и аналогичными аппаратными балансировщиками нагрузки, которые на тот момент представляли распространённую высоконадёжную архитектуру. Однажды инженеры выполнили ручной аварийный переход с Database 1 на Database 2 (первый резерв). Они проделывали это много раз, и всё прошло в точности, как планировалось. Затем, спустя 2 часа, система перестала обслуживать запросы, отображавшиеся на их интерактивных терминалах. После некоторого анализа они решили перезапустить приложения, что привело к проблеме, остановив работу авиалиний примерно на 3 часа.
После аварийного анализа журналов, дампов потоков выполнения и файлов конфигурации инженеры обнаружили проблему в основной системе, а не в той, откуда поступали сообщения об ошибках. Многие потоки оказались заблокированы в ожидании ответа, который так и не поступал (в методе, выполнявшем поиск по городам). Они выяснили, что завершающая инструкция SQL, выполнявшаяся в заключительном блоке, может также выбрасывать SQLException, когда привод пытается запросить от базы освобождение ресурсов. В итоге этот его запрос не обрабатывался, приводя к исчерпанию пула доступных ресурсов.
Что ещё они могли предпринять в этой ситуации? Невозможно предотвратить все ошибки. Некоторые баги неизбежны. Мы лишь можем предотвратить влияние ошибок одной системы на другую.
Книга “Release It!”, Michael T. Nygard
▍ ✈️ Кейс 3. Катастрофа Boeing 737 MAX
В октябре 2018 и марте 2019 года потерпели крушение два Boeing 737 MAX, в результате чего погибло 364 человека. Отчасти причиной этого стала программная система, созданная для повышения безопасности полётов.
MCAS — Maneuvering Characteristics Augmentation System (система повышения манёвренности самолёта) — стала камнем преткновения в истории с Boeing 737 MAX. Созданное из необходимости, это программное обеспечение стало решением внутреннего недочёта дизайна (а именно программным исправлением аппаратной проблемы [1]). Модель 737 MAX, оснащённая расширенными, более экономичными по топливу двигателями, имела иные аэродинамические свойства, нежели её предшественники. MCAS должна была позволить этой модели обрабатывать те же нагрузки, что обрабатывали прежние модели линейки 737, при этом обеспечивая лёгкую адаптацию пилотов к новшеству.
В теории MCAS была элегантна. Она автоматически корректировала горизонтальный стабилизатор, предотвращая сваливание самолёта при крутых поворотах и низких скоростях. На практике же эта система стала наглядным примером того, как единая точка отказа может вызвать каскадный катастрофический эффект.
Критический недочёт? MCAS опиралась на вводные данные лишь одного из двух датчиков угла атаки (angle of attack, ATA) самолёта [2], [3]. В случае передачи этим датчиком некорректных данных произошло бы ложное срабатывание MCAS, что привело бы к наклону носа самолёта вниз даже тогда, когда это не нужно. Дополнительно усложняя проблему, система бы срабатывала повторно, потенциально вводя в недоумение пилотов, которые не были в должной степени осведомлены о её существовании или поведении.
Установив более крупный двигатель, компания Boeing изменила аэродинамические характеристики модели 737. (Источник: NOREBBO.COM)
Однако история про MCAS не будет завершённой без освещения фактов, которые вскрылись после крушений. Впоследствии было заявлено, что компания Boeing поручила значительную часть разработки ПО для модели 737 MAX сторонним инженерам, которым платили всего по $9/час [4]. Как сообщили бывшие инженеры ПО этой компании, она всё больше начинала полагаться на временных сотрудников, в частности, недавних выпускников колледжей, услуги которых предоставляли зарубежные центры разработки ПО. Это решение, скорее всего принятое в свете необходимости сокращения расходов и ускорения разработки, привносит ещё один уровень сложности в историю про Boeing 737 MAX.
Что мы можем почерпнуть из этой истории как инженеры ПО:
- Исключайте единую точку отказа. Случай с MCAS является острым напоминанием о важности наличия в критических системах резервных решений. Использование в качестве ориентира единственного источника данных — в описанном случае датчика AOA — создало уязвимость, которая привела к катастрофическим последствиям. В нашей работе мы всегда должны задаваться вопросом: «Что произойдёт, если этот компонент даст сбой?»
- Делайте ПО и системы простыми, но не слишком простыми (принцип KISS, Keep it simple, stupid). Система MCAS стала ярким примером оверинжиниринга решения аппаратной проблемы. Вместо того чтобы устранить внутреннюю нестабильность в дизайне 737 MAX, компания Boeing решила прибегнуть к программному «исправлению», которое внесло новые уязвимости.
- Отсутствие предметного опыта. Многие инженеры, нанимаемые через сторонние организации, не обладают достаточным опытом в сфере аэрокосмических технологий и критических к безопасности систем. Это привело к проблемам в коммуникации и ошибкам, которые потребовали неоднократных корректировок.
- Слабое тестирование. Несмотря на обширное тестирование, фатальные сбои в MCAS не были обнаружены, пока самолёты не поступали на обслуживание. Это поднимает важные вопросы об адекватности текущего тестирования и практик симуляции, особенно для систем, в которых реальные условия сложно воссоздать полноценно.
Что мы, как программные инженеры, можем сделать для решения перечисленных проблем? Вот некоторые мысли по этому поводу:
- Реализовать подобающую обработку ошибок. Проектируйте свои системы с учётом того, что их компоненты будут давать сбой. Реализуйте резервные механизмы, перекрёстные проверки и предохранительные меры.
- Делать акцент на операторах. Делайте так, чтобы ваши системы отчётливо взаимодействовали с их пользователями, особенно в отношении их текущего состояния и любых автоматизированных действий. Предоставляйте операторам информацию и средства управления, нужные им для принятия взвешенных решений и в случае необходимости переопределения поведения автоматизированных систем.
- Ставить в приоритет интеграционное тестирование. Несмотря на важность модульных тестов, их недостаточно. Не пожалейте времени и средств на всеобъемлющее интеграционное тестирование всей системы. Проверяйте пограничные случаи и состояния отказа.
- Развивать культуру открытого взаимодействия. Создайте среду, в которой люди смогут безбоязненно высказывать свои беспокойства, а аварийные ситуации будут рассматриваться как ценные уроки, а не что-то постыдное, что хочется поскорее замять.
- Отстаивать выделение необходимых ресурсов. Как инженеры ПО, мы запрашиваем те или иные ресурсы для подобающего выполнения своей работы. Это может подразумевать несогласие с нереалистичными дедлайнами, с выделением недостаточного времени на тестирование или с сокращением расходов, которое может поставить под угрозу безопасность [5].