Ariane 5 і перетворення числа, яке знищило ракету

Було корисно?

Якщо ви колись бачили, як чисте розгортання перетворюється на каскад інцидентів, ви вже розумієте емоційну фізику Ariane 5. Все здається нормальним — поки ні. Потім приходить телеметрія, яка читається як абстрактне мистецтво, тривоги починають ланцюжити одна за одною, а панель приладів наполягає, що небо падає, бо, в дуже буквальному сенсі, воно падає.

Це не була містична помилка «ракетна наука складна». Це була помилка інженерії програмного забезпечення з добре знайомим запахом: повторне використання коду, невідповідні припущення, необроблений виняток і «резервування», яке дублювало ту саму помилку двічі. У термінах SRE: відмова спільного режиму з відмінною доступністю — поки в один момент вона перестала бути такою.

Що сталося в перші 40 секунд

4 червня 1996 року перший політ Ariane 5 відбувся з космодрому Куру. Ariane 5 була новим носієм, розробленим для перевезення важчих корисних навантажень, ніж Ariane 4. Нова машина, новий профіль польоту, нова динаміка. Але частина програмного забезпечення — зокрема в інерційній системі відліку (IRS) — була повторно використана з Ariane 4.

Приблизно через 37 секунд після зльоту програмне забезпечення IRS натрапило на числове переповнення під час перетворення з числа з плаваючою комою у цілочисельний тип, який не міг представити більше значення. Це переповнення викликало виняток. Виняток не був оброблений так, як це потрібно для живого апарата. Комп’ютер IRS вимкнувся. Майже одразу резервний IRS також відмовив тим самим способом, бо він запускав той самий код з тими самими припущеннями. Резервування зробило те, для чого було задумане: воно переключилося на ту саму відмову.

Система наведення, тепер позбавлена дійсних даних про орієнтацію, інтерпретувала діагностичні дані як реальні польотні дані. Ракета різко відхилилася від заданої траєкторії. Навантаження на структуру зросли до небезпечних значень. Служба гарантії польоту — побачивши апарат, який вийшов з-під контролю — спрацювала самознищенням. Це не метафора; це межа безпеки. Коли ви запускаєте над населеними територіями, не можна «почекати і подивитися».

Ракети не вибухають тому, що вони сердиті. Вони вибухають тому, що ми наказали їм це зробити навмисно, щоб мінімізувати зону ураження і не допустити потрапляння уламків у небезпечні місця.

Цікаві факти та історичний контекст

  • Політ 501 був першим польотом Ariane 5, тобто не було довгої історії експлуатації, за якою можна було би ховатися — лише симуляції та припущення.
  • Ariane 5 використовувала дві інерційні системи відліку, призначені для резервування, але обидві працювали з ідентичним програмним забезпеченням і піддавалися однаковим вхідним умовам.
  • Змінна, що відмовила, представляла значення, пов’язане з горизонтальною швидкістю з обробки вирівнювання/орієнтації — дійсна для польотного контуру Ariane 4, але не для ранньої траєкторії Ariane 5.
  • Виняток стався під час перетворення з плаваючої коми в ціле число, де цілочисельний тип був занадто малим для представлення значення, викликавши переповнення.
  • Деякі функції IRS працювали після зльоту, хоча вони не були потрібні під час польоту, бо їх відключення вважалося ризикованим або не вартим змін.
  • Компонент наведення обробляв певні діагностичні слова як дані після відмови IRS — класичний сценарій «сміття всередині, агресивний вихід зовні».
  • Система самознищення по команді служби гарантії польоту — це запроектована можливість, а не випадковість — коли наведення втрачено і траєкторія небезпечна, ліквідація апарата захищає людей на землі.
  • Інцидент став еталонним кейсом для інженерії ПЗ, бо відмова була детермінованою, добре задокументованою і болісно уникненною.

Технічна помилка: одне перетворення, одне переповнення, два комп’ютери

Ось суть без міфів: програмне забезпечення IRS намагалося перетворити число з плаваючою комою у знакове 16‑бітне ціле (або аналогічно обмежений цілочисельний тип у реалізації). Значення з плаваючою комою перевищило максимально представлений діапазон цієї цілочисельної типізації. Час виконання викликав виняток переповнення. Цей виняток спричинив вимкнення комп’ютера IRS. Коли він відключився, не було даних про орієнтацію та швидкість. Наведення опинилося «в сліпу».

Чому існувало це перетворення

У багатьох вбудованих системах наведення використовують змішані числові подання. Плаваюча кома застосовується для проміжних обчислень; цілі числа — для фіксованих форматів повідомлень, збереження або продуктивності. Коли системи проектувалися кілька десятиліть тому, пам’ять і цикли ЦП були обмежені. Навіть нині детермінізм важливий: поля із фіксованим розміром у шинах повідомлень або кадрах телеметрії стабільні, тестовані та прості для парсингу.

Тому саме перетворення не було підозрілим. Підозрілим було припущення, вкладене в нього: «це значення завжди поміститься». Це було вірно для польотних умов Ariane 4. Це не було вірно для Ariane 5. Ранній політ Ariane 5 створював більший компонент горизонтальної швидкості, ніж будь-коли у Ariane 4 на той же момент часу. Той самий код, нова фізика.

Чому обробка винятків виявилася фатальною

У системах критичних для безпеки винятки — це не «баги», це сигнали. Переповнення — це програмна еквівалент клапана скидання тиску, що кричить на вас. Або ви обробляєте його так, щоб зберегти безпечну поведінку, або дозволяєте компоненту впасти і сподіватися, що резервування вас підхопить.

Деякі перетворення вони захищали, бо знали, що вони можуть переповнюватися. Але це перетворення не було захищене — бо не очікували, що воно переповниться. Ось пастка: найнебезпечніші відмови — ті, які ви «довели», що не можуть статися, базуючись на минулому.

Що мало б статися замість цього

У ідеальному світі перетворення було б захищеним і насичувальним: обмежувати значення до мін/макс і встановлювати прапорець статусу, або використовувати більший цілочисельний тип, або залишити його як float, або відключити це обчислення після зльоту, якщо воно не потрібне. Оберіть один варіант. Будь‑який з них, реалізований правильно, набагато дешевший, ніж відбудовувати ракету і пояснювати кратер зацікавленим сторонам.

Ось суха й іронічна правда: комп’ютери — дуже буквальні працівники. Вони не «трохи» переповнюються; вони переповнюються точно і після цього зникають без попередження.

Цитата, щоб тримати вас чесними: «Сподівання — не стратегія.» — генерал Гордон Р. Салліван

Чому резервування провалилося: пастка загального режиму

Резервування — це не магія, це математика. Дві системи покращують надійність лише якщо їхні режими відмов досить незалежні. Якщо обидві системи запускають той самий софт, ту саму конфігурацію, однакові числові типи і бачать ті самі входи в один і той самий момент, ви не побудували резервування — ви побудували синхронізовану відмову.

Це називається відмовою загального режиму. Воно трапляється всюди: подвійне живлення з одного щитка, дві кластери Kubernetes, що використовують того самого провайдера DNS, «active-active» бази даних, які заблоковуються тим самим шаблоном запитів.

Ariane 5 мала два блоки IRS. Обидва зазнали того самого переповнення приблизно одночасно. Не було граціозної деградації, немає «один блок переходить у режим зниженої функції», немає незалежної реалізації, немає гетерогенності чи диверсифікації, яка б мала значення.

Також: час перемикання резерву має значення. Якщо резервний блок відмовляє мілісекунди після первинного, ви не переключилися — ви просто додали затримку, що виглядає як збій у логах.

Резервування без диверсифікації — це ковдра комфорту. Вона тепла, поки вогонь її не досягне.

Системний погляд: вимоги, верифікація та «необов’язковий» код

Відмова не була «поганим приведенням типів». Це був ланцюжок рішень, які зробили це приведення фатальним. Проаналізуємо точки прийняття рішень.

1) Повторне використання без перевірки

Повторне використання програмного забезпечення — це добра інженерія. Сліпе повторне використання — це азартна гра. Програмне забезпечення з Ariane 4 було використане в Ariane 5, але верифікація не повністю покривала новий польотний контур. Це той тип скорочення, що в проектному плані виглядає відповідальним: менше змін, менше регресій, менше ризику.

Реальність: менше змін може означати менше уваги. Код стає «достатньо довіреним». У довіреному коді баги люблять виходити на пенсію, а потім повертатися як привиди.

2) Необов’язкові функції, що працюють під час польоту

Частина невдалого обчислення належала до логіки вирівнювання, яка не була потрібна після зльоту. Проте вона продовжувала працювати. Чому? Бо її відключення вимагало змін і повторного тестування, а залишити увімкненою здавалося безпечним через «воно працювало раніше».

Це операційний антипаттерн: дозволяти небезпечній фоні роботі виконуватися в критичному вікні, бо її відключення здається ризикованим. У продуктивних системах ми називаємо це «нешкідливі cron‑завдання», які стають найгучнішими споживачами під час відмови. У ракетах ви не можете зайти через SSH і вбити процес.

3) Політика обробки винятків, що віддавала пріоритет вимкненню

Є випадки, коли вимкнення безпечніше за продовження. Але вимкнення має бути спроектоване як безпечний стан. Для інерційної системи відліку, яка живить наведення, вимкнення не є безпечним, якщо наведення не має альтернативного перевіреного джерела та протокол не запобігає інтерпретації діагностики як правди.

У операціях ми кажемо: швидке відпадання — чудово, коли можна повторити спробу. У ракеті на T+37 секунд ви не можете повторити спробу. Кнопка «перезапустити» — ваше страхове полісе.

4) Верифікація, яка пропустила реальний операційний контур

Це те, що інженери не люблять, бо це не один баг. Це невідповідність між тим, що тестували, і тим, що мало значення. Ви можете мати тисячі тестів і все одно пропустити ту межу, яка визначає реальність: «Чи може ця змінна коли-небудь перевищити 32767?»

І так, це нудно. Саме тому це вбиває системи.

Три корпоративні міні-історії, які ви впізнаєте

Міні-історія 1: Інцидент через неправильне припущення

Компанія з платіжних послуг експлуатувала флот сервісів, що розраховували оцінки ризику шахрайства. Оцінка зберігалася в знаковому 16‑бітному цілом, бо на початку системи вважалося, що потрібно представляти лише 0–10 000. Початкова модель встановлювала обмеження. Усі забули, що кап — це обмеження, а не закон природи.

Нова модель була кращою — вища чутливість, менше хибних негативів. Вона також видавала оцінки вище за старий ліміт у певних пікових випадках. Перетворення глибоко в спільній бібліотеці обрізало або переповнювало значення, які потім картувалися як «надзвичайно низький ризик» через wraparound. Система боротьби зі шахрайством не стала голоснішою. Вона стала тихішою. Це найгірший тип помилки: та, що виглядає як успіх.

Інцидент тривав години, бо моніторинг показував середню оцінку ризику і кількість заблокованих платежів. Середні значення особливо не змінилися. Хвіст експлуатувався. Потрібно було, щоб хтось подивився на сирий гістограмний розподіл, щоб помітити купу транзакцій з нонсенс‑негативними оцінками.

Виправлення було простим: зберігати оцінку як 32‑бітне ціле, додати явні перевірки меж і підтверджувати діапазони виходу моделі як частину деплойменту. Культурне виправлення було складнішим: перестати трактувати «ми ніколи не бачили такого значення» як гарантію. У живій системі майбутнє — це місце, куди йдуть на смерть ваші припущення.

Міні-історія 2: Оптимізація, що обернулася проти

Команда зберігання «оптимізувала» pipeline завантаження, перейшовши з 64‑бітних міток часу на 32‑бітні секунди-від-епохи у індексах, бо це зменшило пам’ять і покращило кешування. Бенчмарки виглядали чудово. Графіки йшли вгору. Була похвала.

Потім вони розширилися в регіон, де деякі пристрої видавали мітки часу набагато в майбутнє через помилку в прошивці годинника. Ці мітки часу переповнювали 32‑бітне представлення. Записи індексувалися в нонсенсові часові відрізки. Запити за «останні 15 хвилин» іноді підтягули майбутні дані, які застосунок трактував як найновіші й, отже, «найавторитетніші». Користувачі бачили, як дані телепортуються в часі.

Розкручування займало дні, бо дані не були зіпсовані в сховищі; вони були «зіпсовані» в індексі. Перебудова індексу вимагала бэкафу мільярдів рядків. Це означало обмеження інжесту, відставання у завантаженні і керівництво, що дивиться на SLA «свіжості даних», яке раптом заграло роль.

Вони повернулися до 64‑бітних, додали валідацію введення і реалізували карантинний шлях: записи з мітками часу за межами діапазону все ще потрапляли, але були помічені, ізольовані і виключені з дефолтних запитів. Урок був прямим: оптимізація, що змінює числові межі — це не оптимізація, а редизайн.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Середня SaaS‑компанія експлуатувала багатокористувацьку базу даних із великими пакетними завданнями. Нічого захопливого — поки оновлення однієї зовнішньої бібліотеки не змінило формат серіалізації. Більшість команд помітили це в продакшні дуже гучно.

Ця команда не помітила. У них була надміру нудна практика: кожне оновлення залежності запускало replay‑тест проти збереженого зразка продуктивного трафіку, і вони зберігали «золоті» артефакти для перевірки сумісності. Replay‑середовище не було ідеальним, але воно було достатньо точним, щоб виявити невідповідності на межах.

Тест виявив тонку проблему: числове поле, що раніше серіалізувалось як 64‑бітне ціле, тепер декодувалося як 32‑бітне в одному споживачі через неоднозначність схеми. При нормальних діапазонах це працювало. При рідкісних великих значеннях воно переповнювалося і запускало петлю повторних спроб. Та петля перетворилася б на гуркітну атаку на базу даних.

Вони виправили схему, закріпили версії та випустили оновлення. Ніхто поза командою не помітив. Ось у чому суть. Робота з надійності часто непомітна. Якщо ваша практика надійності гламурна, швидше за все ви займаєтеся реагуванням на інциденти, а не інженерією.

Практичні завдання: команди, виводи та рішення

Відмова Ariane 5 — це проблема числових меж, загорнута в проблему верифікації та дисципліни експлуатації. Ось конкретні завдання, які ви можете виконати в реальних системах, щоб запобігти тому самому класу помилок. Кожне завдання включає: команду, приклад виводу, що це означає, і рішення, яке потрібно прийняти.

1) Знайти звужуючі перетворення в збірках C/C++ (попередження компілятора)

cr0x@server:~$ make CFLAGS="-O2 -Wall -Wextra -Wconversion -Wsign-conversion" 2>&1 | head -n 8
src/nav.c:214:23: warning: conversion from ‘double’ to ‘int16_t’ may change value [-Wfloat-conversion]
src/nav.c:215:18: warning: conversion to ‘int16_t’ from ‘int’ may change the sign of the result [-Wsign-conversion]
...

Що означає вивід: Компілятор точно повідомляє, де ви можете отримати переповнення, обрізання або зміну знака.

Рішення: Ставте ці попередження як блокер релізу для коду критичного для безпеки або грошей. Додайте явні перевірки меж або розширте типи. Якщо необхідно звужувати, документуйте межі й забезпечуйте їх дотримання.

2) Полювання на ризиковані приведення в репозиторії (швидкий grep)

cr0x@server:~$ rg -n "(\(int16_t\)|\(short\)|\(int\))\s*\(" src include
src/irs/align.c:88:(int16_t)(h_velocity)
src/irs/align.c:131:(short)(bias_estimate)

Що означає вивід: Явні приведення — це місця, де зустрічаються намір і небезпека.

Рішення: Перегляньте кожне приведення: який очікуваний діапазон, що відбувається при його перевищенні і чи виконується воно у критичному вікні.

3) Виявлення необроблених винятків/крашів через systemd‑логи

cr0x@server:~$ journalctl -u navd --since "1 hour ago" | tail -n 12
Jan 22 10:11:04 stage navd[1827]: converting float to int16: value=40211.7
Jan 22 10:11:04 stage navd[1827]: FATAL: SIGABRT after overflow trap
Jan 22 10:11:04 stage systemd[1]: navd.service: Main process exited, code=killed, status=6/ABRT
Jan 22 10:11:04 stage systemd[1]: navd.service: Failed with result 'signal'.

Що означає вивід: Числове перетворення викликало trap, що зупинило сервіс.

Рішення: Визначте, чи сервіс має fail‑stop чи деградувати. Для критичних контрольних циклів спроектуйте безпечний режим; для безстанних сервісів реалізуйте повтори й circuit breakers.

4) Перевірити пастки ядра/ЦП для крашів, схожих на переповнення (core dump увімкнено)

cr0x@server:~$ coredumpctl list navd | tail -n 3
TIME                            PID   UID   GID SIG COREFILE  EXE
Jan 22 10:11:04                 1827  1001  1001  6 present   /usr/local/bin/navd

Що означає вивід: Є core dump, який можна дослідити замість гадань.

Рішення: Витягніть core, ідентифікуйте точне перетворення та вхідний діапазон і додайте регресійний тест для цієї межі.

5) Виміряти, чи «резервні» сервіси відмовляють синхронно (корельована відмова)

cr0x@server:~$ awk '$3=="ERROR" {print $1,$2,$6}' /var/log/irs-a.log | tail -n 5
2026-01-22 10:11:04 overflow value=40211.7
2026-01-22 10:11:04 shutdown reason=exception
cr0x@server:~$ awk '$3=="ERROR" {print $1,$2,$6}' /var/log/irs-b.log | tail -n 5
2026-01-22 10:11:04 overflow value=40212.1
2026-01-22 10:11:04 shutdown reason=exception

Що означає вивід: Та сама мітка часу, та сама причина: відмова загального режиму.

Рішення: Впровадьте диверсифікацію: різні реалізації, різна верифікація, різні пороги або хоча б рознесена поведінка (один обмежує, інший — сигналізує).

6) Перевірити числові діапазони на межі (runtime‑захисні механізми)

cr0x@server:~$ python3 - <<'PY'
import math
MAX_I16=32767
vals=[120.0, 32766.9, 40000.1]
for v in vals:
    ok = -MAX_I16-1 <= v <= MAX_I16
    print(f"value={v} fits_int16={ok}")
PY
value=120.0 fits_int16=True
value=32766.9 fits_int16=True
value=40000.1 fits_int16=False

Що означає вивід: Ви можете виявити умови переповнення до перетворення.

Рішення: Запровадьте захисні перевірки на інтерфейсах: якщо поза діапазоном — обрізати, відкидати або направляти в шлях безпечної деградації з тривогою.

7) Підтвердити, що ваша телеметрія не «сміття, інтерпретоване як правда» (перевірки схеми)

cr0x@server:~$ jq -r '.frame_type, .attitude.status, .attitude.roll' telemetry/latest.json
DIAGNOSTIC
FAIL
-1.7976931348623157e+308

Що означає вивід: Діагностичний кадр парситься як кадр орієнтації, і sentinel‑значення просочується.

Рішення: Зробіть типи повідомлень явними і валідованими. Відмовляйтеся приймати кадри, що не відповідають схемі та стану.

8) Виявляти події насичення/обрізання (ви хочете їх бачити)

cr0x@server:~$ grep -R "SATURAT" -n /var/log/navd.log | tail -n 5
41298:WARN SATURATION h_velocity=40211.7 clamped_to=32767
41302:WARN SATURATION h_velocity=39880.2 clamped_to=32767

Що означає вивід: Система стикалася зі значеннями за межами очікуваного діапазону, але залишалася живою.

Рішення: Розберіться, чому діапазон перевищено. Вирішіть, чи прийнятне обрізання, чи воно ховає реальну зміну моделлю/фізикою.

9) Перевірити, що «необов’язкові» завдання не запускаються в критичне вікно

cr0x@server:~$ systemctl list-timers --all | head -n 12
NEXT                         LEFT     LAST                         PASSED    UNIT                         ACTIVATES
Wed 2026-01-22 10:12:00 UTC  32s      Wed 2026-01-22 10:07:00 UTC  4min ago  rotate-alignment.timer       rotate-alignment.service
Wed 2026-01-22 10:15:00 UTC  3min 32s Wed 2026-01-22 10:00:00 UTC  11min ago logrotate.timer             logrotate.service

Що означає вивід: Таймери спрацьовують під час вашого критичного періоду.

Рішення: Вимкніть або перенаграйте неважливі завдання під час запусків/пікових вікон. «Фонова» робота — фонова лише доки не станеться інше.

10) Підтвердити, що перемикання працює під навантаженням (health і readiness)

cr0x@server:~$ kubectl get pods -n guidance -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE
irs-a-7c6b9f9c7b-2m8qk   1/1     Running   0          3d    10.42.1.12   node-1
irs-b-7c6b9f9c7b-q9d2p   1/1     Running   0          3d    10.42.2.19   node-2
cr0x@server:~$ kubectl describe svc irs -n guidance | sed -n '1,30p'
Name:              irs
Namespace:         guidance
Selector:          app=irs
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.43.128.10
Endpoints:         10.42.1.12:9000,10.42.2.19:9000

Що означає вивід: У вас є два endpoints, але це не доводить їх незалежність при відмові.

Рішення: Запустіть хаос‑тести, що інжектують конкретний шлях відмови (переповнення) і перевірте, що споживач відкидає некоректні дані і продовжує працювати безпечно.

11) Виявляти корельовані перезапуски (ознака загального режиму)

cr0x@server:~$ kubectl get events -n guidance --sort-by=.lastTimestamp | tail -n 10
10m   Warning   BackOff     pod/irs-a-7c6b9f9c7b-2m8qk   Back-off restarting failed container
10m   Warning   BackOff     pod/irs-b-7c6b9f9c7b-q9d2p   Back-off restarting failed container
10m   Normal    Pulled      pod/irs-a-7c6b9f9c7b-2m8qk   Container image pulled
10m   Normal    Pulled      pod/irs-b-7c6b9f9c7b-q9d2p   Container image pulled

Що означає вивід: Обидва репліки падають з тієї самої причини одночасно.

Рішення: Перестаньте вважати, що репліки — це резильєнтність. Впровадьте незалежні версії, feature flags або поетапні релізи з канаpями.

12) Проінспектувати числові межі в схемах повідомлень (приклад protobuf)

cr0x@server:~$ rg -n "int32|int64|sint32|sint64|fixed32|fixed64" schemas/attitude.proto | head -n 20
12:  int32 roll_millirad = 1;
13:  int32 pitch_millirad = 2;
14:  int32 yaw_millirad = 3;
18:  int32 h_velocity_cm_s = 7;

Що означає вивід: Ви кодуєте швидкість в int32 сантиметри/секунду. Добре. Але потрібно підтвердити максимальні значення з запасом.

Рішення: Запишіть максимально можливе фізично значення (плюс коефіцієнт безпеки), потім підтвердіть, що тип може це представити в усіх режимах місії.

13) Запровадити property‑тести для числових меж (boundary fuzzing)

cr0x@server:~$ pytest -q tests/test_numeric_bounds.py -k "int16_guard"
1 passed, 0 failed, 0 skipped

Що означає вивід: У вас є автоматичне покриття, яке перевіряє значення поблизу та за межами кордонів.

Рішення: Вимагайте ці тести для будь‑якого коду, що перетворює типи або серіалізує телеметрію/керуючі слова.

14) Переконатися, що поведінка безпечного режиму досяжна (feature flag / режим)

cr0x@server:~$ curl -s localhost:9000/status | jq
{
  "mode": "SAFE_DEGRADED",
  "reason": "overflow_guard_triggered",
  "attitude_valid": true,
  "velocity_valid": false
}

Що означає вивід: Компонент не впав; він перейшов у деградований режим і чітко позначив, що дійсне.

Рішення: Надавайте перевагу явній деградації над неявним відмовлянням. Зробіть так, щоб споживачі дотримувалися прапорців валідності або відмовлялися від неприпустимих полів.

Друга коротка жартівлива ремарка (остання): Якщо ваші перевірки діапазонів «будуть додані пізніше», вітаю — ви винайшли список TODO, що живиться ракетним паливом.

План швидкої діагностики

Коли система раптово входить у нонсенсову поведінку — різкі маневри у контурі керування, сміттєва телеметрія, каскадні перезапуски — не починайте з переписування коду. Почніть з пошуку вузького місця та межі. Ось порядок тріажу, що працює для ракет і веб‑сервісів.

1) Підтвердити режим відмови: краш, помилковий вивід чи неправильна інтерпретація

  • Краш: процеси виходять, піди перезапускаються, systemd повідомляє сигнали. Шукайте traps, abort, винятки.
  • Помилковий вивід: сервіс залишається активним, але видає неможливі значення (негативні там, де неможливо, NaN, екстремальні float).
  • Неправильна інтерпретація: споживач неправильно парсить кадри або трактує діагностику як дані.

2) Перевірити на відмову загального режиму між резервами

Якщо первинний і резервний відмовляють у межах секунд один від одного, вважайте, що причина спільна: спільний бінарник, спільна конфігурація, спільна залежність, спільний шаблон входів. Резервування — не корінна причина; це підказка.

3) Виявити межу, яка змінилася

Запитайте: яке значення стало більшим, меншим або змінились одиниці? Нова траєкторія, нове навантаження, новий клас клієнтів, нова модель, нове обладнання. Саме тут жила помилка Ariane 5: новий польотний контур підживлював старі числові припущення.

4) Підтвердити контракт даних на інтерфейсах

Більшість катастроф відбуваються між компонентами: серіалізація, дрейф схеми, несумісність одиниць, silent truncation. Підтвердіть тип повідомлення і числові діапазони на кордоні.

5) Лише потім оптимізуйте або рефакторіть

Оптимізація часто забирає запас безпеки (менші типи, менше перевірок, швидші шляхи). Під час діагностики вам потрібна краща спостережуваність і більше перевірок, а не менше. Спочатку виправте, потім пришвидште, потім здешевіть.

Типові помилки: симптом → корінь → виправлення

Симптом: «У симуляції працювало, у продакшні відмовляє»

Корінь: Симульований контур не включав критичні межі (або використовував Ariane 4‑подібні значення).

Виправлення: Розширьте тестові вектори до крайніх, але фізично можливих значень; додайте property‑тести; вимагаєте доказів меж для будь‑якого звужуючого перетворення.

Симптом: Первинний і резервний падають майже одночасно

Корінь: Відмова загального режиму: ідентичне ПЗ/конфіг і однакові входи.

Виправлення: Додайте диверсифікацію (інша реалізація або версія), рознесіть поведінку (один обрізає, інший вимикається), і перевірте переключення під інжекцією конкретної відмови.

Симптом: Нижчестоячі системи приймають божевільні рішення після відмови вгорі

Корінь: Діагностичні або недійсні дані інтерпретуються як валідні через слабкі контракти.

Виправлення: Додайте явні типи кадрів і прапорці валідності; відкидайте недійсні дані; для керування віддавайте перевагу fail‑closed; у важливих шляхах уникайте «best effort» парсингу.

Симптом: «Необов’язкове» фонове завдання спричиняє інциденти в критичні вікна

Корінь: Непотрібні обчислення все ще виконуються в критичній фазі, споживають CPU або заходять у рідкісні кодові шляхи.

Виправлення: Вимикайте або обмежуйте необов’язкові завдання після переходів стану; використовуйте розклад, орієнтований на режим; доведіть, що шляхи коду після зльоту мінімальні.

Симптом: Невелика зміна коду спричиняє масштабну нестабільність

Корінь: Ви змінили числове подання, одиниці або поведінку насичення; система покладалася на недокументовані припущення.

Виправлення: Розглядайте зміни типів як зміни інтерфейсів. Версіонуйте контракт. Додайте тести сумісності та replay трафіку.

Симптом: Моніторинг показує «нормальні середні», а насправді все зламано

Корінь: Хвіст (рідкісні переповнення) прихований середніми значеннями; бракує гістограм/перцентилів.

Виправлення: Моніторьте розподіли, а не лише середнє. Відстежуйте min/max, перцентилі та кількість подій насичення/відкинутих недійсних значень.

Контрольні списки / поетапний план

Контрольний список: Запобігання помилкам переповнення в числах у коді критичного для безпеки чи грошей

  1. Інвентаризація числових перетворень (float→int, int→менший int, перетворення одиниць). Якщо ви не можете їх перелічити, ви не можете ними керувати.
  2. Визначте очікувані діапазони з запасом для кожного значення, включно з «новими режимами місії» (нова машина, новий регіон, нова модель, новий клас клієнтів).
  3. Робіть перетворення явними і захищеними: перевірка меж, обрізання/насичення і логування структурованої події.
  4. Стандартизуйте одиниці в інтерфейсах. Якщо потрібно конвертувати — робіть це на краю і записуйте одиницю в схемі.
  5. Визначте політику відмов: падіння, деградація, обрізання або відкидання. Для систем керування — віддавайте перевагу безпечній деградації з чіткими прапорцями валідності.
  6. Тестуйте за межами контуру: не лише «макс очікуваний», а й «макс правдоподібний», плюс adversarial‑значення (NaN, infinity, від’ємні, дуже великі).
  7. Перевірте незалежність резервування: різні версії, різні опції компілятора, різні шляхи коду або хоча б різна поведінка при переповненні.
  8. Вимагайте контрактних тестів між виробником і споживачем, включно зі схемою, одиницями та межами значень.
  9. Спостерігайте обрізання: дашборди для «подій насичення» і «відкинутих недійсних даних». Вони повинні бути майже нульовими і розслідуватися.
  10. Проведіть game‑day, який інжектує точний сценарій переповнення і доводить, що система залишається безпечною (а не лише «up»).

Контрольний список: Повторно використовувати код, не імпортуючи старі припущення

  1. Перелічіть кожний повторно використаний модуль і його початковий операційний контур.
  2. Для кожного модуля запишіть, що змінилося в новій системі (входи, діапазони, таймінг, одиниці, продуктивність).
  3. Повторно прогоніть верифікацію проти нових контурів; не приймайте «вже кваліфіковано» як аргумент.
  4. Видаліть або відключіть логіку, що не потрібна в критичні фази. Менше коду в роботі — менше сюрпризів.
  5. Документуйте раціонал для будь‑якого незахищеного перетворення: чому воно не може переповнитися і що це забезпечує.

Поширені запитання

1) Чи була відмова Ariane 5 «всього лише переповненням цілого числа»?

Ні. Переповнення було тригером. Відмова була системною: повторне використання без перевірки, небажана фоновa робота під час польоту, обробка винятків, що вимикала критичний компонент, і споживачі, що неправильно інтерпретували недійсні виходи.

2) Чому резервна інерційна система не врятувала ракету?

Бо вона впала так само, з тієї самої причини, в той самий час. Це відмова загального режиму. Резервування допомагає лише коли відмови незалежні.

3) Навіщо взагалі використовували перетворення float→int?

Поля фіксованого розміру звичні для повідомлень, форматів телеметрії і детермінованої продуктивності. Перетворення нормальне. Небезпечним було відсутність захисту від виходу за межі представлення.

4) Хіба не могли просто «поймати виняток» і продовжити?

Могли б, але «продовжити» має означати «продовжити безпечно». Варіанти: обрізати зі встановленням прапорця валідності, переключитися в деградований режим або відключити це обчислення після зльоту.

5) Чому наведення так агресивно відреагувало після відмови IRS?

Наведення потребує даних про орієнтацію і швидкості. Коли воно отримало недійсні дані (або діагностику, що трактована як дані), воно обчислило неправильні керуючі команди. У замкненому контурі керування погані входи можуть дуже швидко породити агресивні виходи.

6) Який урок SRE з цього?

Припущення — це залежності продакшну. Якщо ви повторно використовуєте компоненти, ви маєте перевіряти припущення під новими навантаженнями. А також: моніторте граничні події, а не лише доступність.

7) Чи «fail fast» погано?

Fail fast — чудово, коли можна повторити спробу або обійти відмову. У системах, де повтору немає (системи керування, системи безпеки, незворотні дії), потрібна безпечна деградація і суворі правила валідності даних.

8) Як запобігти «діагностиці, що інтерпретується як дані» у розподілених системах?

Використовуйте явні типи повідомлень, валідацію схем, версіоновані контракти і оборонні споживачі, які відмовляються діяти на недійсні або несподівані кадри. Розглядайте помилки парсингу як події рівня безпеки.

9) Чи більше тестів виправить цей клас проблем?

Лише якщо тести включають правильні межі. Десять тисяч «happy path» тестів не знайдуть одного пропущеного захисту від переповнення. Зосередьтеся на тестах контуру, fuzz‑інгіх біля числових меж і контрактних тестах між інтерфейсами.

10) Яке резервування правильне?

Резервування з незалежністю: різні реалізації, різні версії, різні компілятори, різні модальності сенсорів або хоча б різна поведінка при помилці. Якщо обид сторони мають той самий баг — ви купили два квитки на одну й ту саму катастрофу.

Наступні кроки, які вам варто зробити

Урок Ariane 5 — не «не робіть помилок». Він — «перестаньте довіряти припущенням, які не забезпечені». Ракета — це просто дуже дорога продуктивна система з меншою кількістю варіантів відкату.

  1. Цього тижня інвентаризуйте числові перетворення у ваших критичних системах. Якщо ви не знаєте, де вони — ви не зможете їх захистити.
  2. Увімкніть суворі попередження компілятора і зробіть звужуючі перетворення обов’язковим пунктом рев’ю.
  3. Додайте runtime‑захисні механізми на інтерфейсах: перевірки меж, обрізання з прапорцями або відкидання з тривогою.
  4. Доведіть незалежність резервування шляхом інжекції відмов. Якщо обид репліки падають разом — назвіть це по імені: одна система, дублікат.
  5. Вимкніть необов’язкову роботу в критичних вікнах. Якщо після зльоту вона не потрібна — не запускайте її після зльоту.
  6. Моніторте розподіли і граничні події (кількість насичень, відкинутих недійсних кадрів), а не лише середні значення й uptime.

Зробіть це — і ви не просто уникнете моменту, як у Ariane 5. Ви також будете випускати швидше, спати краще і менше ранків пояснювати керівництву, чому графіки виглядали нормально, поки насправді все горіло.

← Попередня
Шифрування резервних копій Docker: захист секретів без порушення відновлення
Наступна →
Debian/Ubuntu «Підключено, але немає інтернету»: виправлення маршрутизації, шлюзу та політики маршрутизації

Залишити коментар