Подробные статьи о технологиях, определяющих будущее.

Разработка ПО, когда сервер нельзя перезагрузить

Космическое ПО не может тихо упасть и перезапуститься. Как инженеры пишут код, где ошибка стоит миссии за миллиарды, а перезагрузка занимает часы.

Одинокий космический аппарат с печатными платами на орбите красной планеты, далеко от Земли

Ваш веб-сервер падает в 3 часа ночи. Kubernetes его перезапускает. Пользователи видят короткую страницу с ошибкой. Никого не уволили. Теперь представьте, что ваш сервер вращается вокруг Марса, перезапуск занимает 45 минут (всё это время нет управления ориентацией, терморегуляции и связи), «пользователь» — космический аппарат за 2,5 миллиарда долларов, а Kubernetes нет. Есть только ваш код и процессор, устойчивый к радиации, на котором он работает.

Космическое ПО работает в условиях, на фоне которых обычная инженерия выглядит безобидно. Нельзя выкатить хотфикс. Нельзя зайти по SSH и посмотреть логи. Нельзя добавить серверов, когда растёт нагрузка. Каждая строка кода должна быть верной до запуска, потому что потом софт предоставлен сам себе на годы или десятилетия, работает на железе, которое постепенно повреждается радиацией, и связывается с Землёй через канал с задержками в несколько минут и пропускной способностью в килобиты в секунду.

Ограничения, которые определяют всё

Радиация. В космосе высокоэнергетические частицы постоянно бомбардируют электронику. Одна частица может перевернуть бит в памяти (так называемое single-event upset, или SEU), повредить регистр или заблокировать процессор. Это не редкость: спутники на низкой орбите фиксируют тысячи битовых сбоев в день. Для космоса используют радиационно стойкие компоненты, которые медленнее, дороже и на поколения отстают от потребительского железа. Процессор марсохода Perseverance — RAD750, примерно соответствующий PowerPC 1998 года на 200 МГц.

Задержки связи. Радиосигналу до Марса от 4 до 24 минут (в зависимости от взаимного положения планет), до Юпитера — от 33 до 54 минут. Дело не только в задержке: космический аппарат должен самостоятельно справляться с любой проблемой как минимум за время кругового обмена сигналами, прежде чем наземный центр вообще её увидит, не говоря уже о команде. Когда у зондов «Вояджер» возникла аномалия на расстоянии более 22 световых часов от Земли, софт почти двое суток принимал решения сам.

Нет физического доступа. Нельзя заменить сломанный компонент, добавить оперативной памяти или поменять жёсткий диск. Если основной компьютер умирает, а резервный не работает, миссия окончена. Каждый режим отказа нужно предусмотреть в софте до запуска.

Чем космический код отличается

Космическое ПО использует техники, которые в обычной разработке сочли бы абсурдным перебором.

Тройное модульное резервирование (TMR). Критичные вычисления выполняются трижды на трёх независимых процессорах. Голосующий узел сравнивает три результата и берёт ответ большинства. Если один процессор выдал неверный результат из-за радиации, два других его перевешивают. Некоторые системы для надёжности запускают пять копий (пентамодульное резервирование).

Скрабинг памяти. Фоновый процесс постоянно читает память, проверяет её по кодам коррекции ошибок (ECC) и исправляет одиночные битовые ошибки, пока они не накопились в неисправимые многобитовые. Работает непрерывно: каждый байт памяти проверяется и исправляется несколько раз в секунду.

Сторожевые таймеры. Аппаратные таймеры, которые софт должен регулярно сбрасывать. Если программа зависла (например, из-за радиационной блокировки), таймер истекает и запускает аппаратный сброс. Софт нужно проектировать так, чтобы он переживал неожиданные перезагрузки в любой точке выполнения: любое вычисление может прерваться и начаться заново.

// Simplified space software patterns
// Watchdog — must be kicked regularly or hardware resets
void main_loop(void) {
while (1) {
kick_watchdog();  // Reset the timer — we're alive
// All operations must complete within watchdog timeout
read_sensors();
kick_watchdog();
run_attitude_control();
kick_watchdog();
check_thermal_limits();
kick_watchdog();
process_ground_commands();
kick_watchdog();
}
}
// Critical value stored with redundancy
typedef struct {
int32_t value_a;  // Primary copy
int32_t value_b;  // Redundant copy
int32_t value_c;  // Third copy for voting
} RedundantInt;
int32_t read_redundant(RedundantInt *r) {
// Majority vote — tolerates one corrupted copy
if (r->value_a == r->value_b) return r->value_a;
if (r->value_a == r->value_c) return r->value_a;
if (r->value_b == r->value_c) return r->value_b;
// All three differ — flag anomaly
raise_anomaly(MEMORY_CORRUPTION);
return r->value_a;  // Best guess
}

Тестирование и есть продукт

По оценке Лаборатории реактивного движения NASA (JPL), тестирование космического ПО съедает 60–80% всех затрат на разработку. Не 60% времени, а 60% общей стоимости. Тестирование обходится дороже самой разработки, потому что корректность нужно доказать с такой строгостью, до которой обычное тестирование даже близко не подходит.

Должен быть протестирован каждый путь выполнения. Не «высокое покрытие кода» в том смысле, как это понимают веб-разработчики, а буквально каждый путь через каждую функцию, включая обработку ошибок, таймаутов и аппаратных сбоев. 100% покрытие ветвлений — это отправная точка, а не цель.

Помимо юнит-тестов, космическое ПО проходит аппаратно-внутрисхемное тестирование (hardware-in-the-loop: реальный софт на реальном полётном железе с имитацией космической среды), долгие стресс-тесты (работа в течение месяцев, чтобы найти баги, зависящие от времени) и тестирование с внесением сбоев (fault injection): намеренно портят память, убивают процессоры и обрывают каналы связи, чтобы проверить, что софт восстанавливается.

Формальная верификация всё чаще применяется к самым критичным компонентам. Вместо проверки работы кода на конкретных входных данных она математически доказывает, что код работает для всех возможных входов. Это дорого и медленно, но для кода, который управляет ориентацией (attitude) аппарата или его двигательной установкой, затраты оправданы.

Что всё равно идёт не так

Несмотря на всю строгость, космическое ПО всё равно ломается. Эти провалы поучительны, потому что показывают пределы даже самой аккуратной инженерии.

  • Mars Climate Orbiter (1999) разбился, потому что одна команда использовала имперские единицы, а другая — метрические. Софт был корректен, неверными были требования. Никакое тестирование не поймает неверную спецификацию.
  • Ariane 5, полёт 501 (1996) взорвалась, потому что 64-битное число с плавающей точкой преобразовали в 16-битное целое, и произошло переполнение. Код переиспользовали из Ariane 4, где значение никогда не выходило за пределы 16 бит. Для Ariane 4 код был корректным, для Ariane 5 — катастрофически неверным.
  • Mars Polar Lander (1999), вероятно, разбился, потому что вибрация датчика при выпуске опор была принята за касание грунта, и двигатели выключились на высоте 40 метров. Проблема была в зависящей от времени интерпретации показаний датчика, которую тесты не поймали, поскольку не воспроизводили точно характер вибрации.
  • Первоначальный дефект зеркала Хаббла (1990) был не программной ошибкой: зеркало отшлифовали до неверной формы из-за неправильно откалиброванного измерительного инструмента. Ошибка была в самом инструменте для тестирования.

Общая черта: код соответствовал спецификации, но спецификация не соответствовала реальности. Такие баги предотвращать сложнее всего, потому что они возникают в зазоре между моделью и реальным миром.

Чему может научиться наземное ПО

Большинство из нас не пишет космическое ПО. Но некоторые из этих практик напрямую применимы для построения надёжных систем на Земле.

Проектируйте с расчётом на перезапуск. Космическое ПО исходит из того, что его могут перезагрузить в любой момент, и оно должно вернуться в известное корректное состояние. У веб-сервисов должно быть то же свойство: если сервер упал и перезапустился, восстанавливается ли он без ручного вмешательства? Продолжает ли он обработку с того места, где остановился, или теряет работу? Паттерны защитного проектирования, которые космические инженеры считают обязательными, в веб-разработке часто необязательны, хотя так быть не должно.

Тестируйте сбои, а не только счастливый путь. Тестирование космического ПО целенаправленно вносит сбои: убивает процесс, портит память, обрывает сетевые соединения, возвращает ошибки из каждого системного вызова. Большинство тестирования веб-приложений проверяет, работают ли фичи, а не то, как система переживает сбои.

Резервируйте критичные данные. Если ваше приложение хранит данные, которые дорого восстанавливать (финансовые записи, пользовательский контент, состояние конфигурации), храните их с резервированием и регулярно проверяйте. Репликация БД — очевидный пример, но проверки внутри приложения (контрольные суммы, валидация, периодические проверки целостности) ловят повреждения, которые репликация просто распространяет дальше.

Сторожевые механизмы для критичных процессов. Если процесс не должен зависать, за ним нужно следить. Не просто «процесс запущен», а «процесс продвигается вперёд». Health-проверки, которые подтверждают, что система реально работает (обрабатывает запросы, обновляет состояние, укладывается в дедлайны), ловят сбои, которые пропускает мониторинг на уровне процессов.

Новое космическое ПО

Коммерческая космонавтика (SpaceX, Rocket Lab, Planet) ломает некоторые из этих традиций. SpaceX использует Linux и C++ на бортовых компьютерах Falcon 9, что по меркам традиционной аэрокосмической отрасли считается ересью. Planet Labs управляет сотнями небольших спутников и относится к ним скорее как к распределённой системе, а не как к штучному железу: если спутник выходит из строя, созвездие компенсирует потерю.

Это отражает более широкий вопрос: какая именно надёжность вам нужна? Марсоход за 2,5 миллиарда долларов оправдывает пять лет тестирования. Спутник связи стоимостью 500 000 долларов, один из сотен в созвездии, оправдывает гораздо меньше. Инженерная практика должна соответствовать профилю риска, а не слепо следовать традициям, сложившимся для другой эпохи.

Но главный урок сохраняется при любом бюджете: ПО, до которого нельзя физически добраться после развёртывания, должно быть надёжнее того, до которого можно. Запускаете ли вы космический аппарат, обновляете ли парк IoT-устройств или работаете с сетью edge-вычислений, принцип один: если вы не можете зайти по SSH и всё починить, софт должен справиться сам. Такой образ мышления, применённый разумно, делает всё ПО лучше.