Mars Climate Orbiter: Розбіжність одиниць, яка призвела до втрати космічного апарата

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

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

Mars Climate Orbiter — канонічний приклад: космічний апарат загинув через те, що одна частина системи видавала
дані в імперських одиницях, а інша очікувала метричні. Якщо ви думаєте «у нас так ніколи не станеться» —
вітаю: саме таке мислення робить цю помилку ймовірною у вашій організації.

Що сталося насправді (і чому це мало значення)

Mars Climate Orbiter (MCO) було запущено 1998 року й він мав досліджувати клімат Марса: атмосферу, пил,
водяну пару, сезонні зміни. Також виконував роль ретранслятора зв’язку для спорідненого посадкового апарата. Та він
так і не встиг виконати жодної з цих задач. Під час входу на орбіту Марса у вересні 1999 року апарат пролетів занадто низько,
ймовірно відчув атмосферні сили, для яких не був розрахований, і був втрачений. Або згорів, або вискочив на орбіту навколо Сонця;
в операціях «втрачений» — ввічливе слово для «ми не знаємо, де він і не можемо з ним зв’язатися».

Корінна причина, простими інженерними словами: навігаційна команда використала дані про продуктивність двигунів,
виражені в pound-force seconds, тоді як навігаційне програмне забезпечення очікувало newton seconds. Той самий фізичний
концепт (імпульс). Інша система одиниць. Ця невідповідність створила постійне зрушення в оцінках траєкторії.
Корабель не зробив чогось раптово божевільного. Він послідовно робив помилку достатньо довго, щоб вона стала фатальною.

І ось операційний урок, який більшість пропускає: це не був «друк помилки». Це була відмова дисципліни інтерфейсів.
Система мала кілька можливостей це виявити — огляди, перевірки, тренди телеметрії, симуляції, реакція на аномалії.
Відмова не була однією помилкою; це був стек рішень без виправлень, який дозволив помилці вижити.

Жарт №1: Невідповідності одиниць — єдині баги, які можна виправити або компілятором, або рулеткою.

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

  • Частина програми Mars Surveyor ’98: MCO був у парі з Mars Polar Lander, у рамках підходу «швидше, краще, дешевше».
  • Запуск на ракеті Delta II: Надійна ракета; сам запуск не був проблемою.
  • Втрата сталася під час входу на орбіту: Найризикованіша фаза: вузьке вікно, де точність навігації є питанням виживання.
  • Невідповідність стосувалася даних про імпульс: Малі корекції траєкторії залежали від точного моделювання включень двигунів.
  • Імперська vs метрична: Конкретно: pound-force seconds (lbf·s) проти newton seconds (N·s). Один lbf ≈ 4.44822 N.
  • Операційний тренд був видимим: Рештки навігації та прогнози траєкторій дрейфували; система була «не в нулі» так, що мала зупинити й перевірити.
  • Контроль інтерфейсів має значення: Очікування щодо одиниць було задокументоване; його не виконували та не перевіряли.
  • Тиск програми був реальним: Обмеження бюджету й графіка зменшили надлишковість і зробили «ми це виправимо пізніше» культурно допустимим.

Анатомія відмови: де система брехала

Число не було неправильним; значення було

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

Невідповідності одиниць особливо підступні, бо вони правдоподібні. Імпульс двигуна «10» може означати 10 N·s
або 10 lbf·s. Жодне з них не виглядає абсурдним. І якщо нижча система очікує одне, а отримує інше,
помилка масштабно лінійна — без драматичних сплесків, без негайного хаосу, лише постійний зсув.

Зсув перемагає шум

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

Інтерфейси — місце, куди йдуть гинути надійність

Найнебезпечніший код у будь-якій системі — це код, який «просто» перетворює дані між двома підсистемами. Шар відображення.
Завдання ETL. Адаптер. Скрипт, що перезаписує поле. Обгортка, яка «поправляє» API. Саме там зникає типова інформація,
там обґрунтовують семантику, там рідшає покриття тестами, і де «всі знають» стає заміною перевірки.

У MCO інтерфейс між моделюванням тяги/операційними даними й навігаційним ПЗ фактично занулював семантику до чисел.
Люди «знали» одиниці. Система — ні. Коли люди мінялись або припущення дрейфували, система не мала запобіжників.

Цитата про надійність, яку варто приклеїти до монітора

Gene Kranz (директор польотів NASA) казав: «Failure is not an option.» Якщо ви бачите це на плакаті,
ви також бачили, як люди використовують це замість інженерії. Ось доросла версія: невдача завжди можлива.
Ваше завдання — зробити так, щоб дрібні помилки важко перетворювалися на катастрофічні.

Передачі, інтерфейси та «границя одиниць»

Поговорімо, що справді означає «невідповідність одиниць» в сучасній організації. Це не лише метрична проти імперської системи.
Це будь-яка семантична невідповідність:

  • мілісекунди проти секунд
  • байти проти кібібайтів
  • UTC проти місцевого часу
  • включні проти виключних інтервалів
  • знакові проти беззнакових значень
  • швидкість за секунду проти за хвилину
  • стиснений проти нестисненого розміру
  • «customer_id» як акаунт проти користувача проти домогосподарства

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

ICD — це не паперова робота; це виконувані обмеження

Космічні програми використовують Interface Control Documents (ICD), щоб вказати, як системи спілкуються: формати, таймінг,
одиниці, допуски. В корпоративних системах ми часто маємо те саме в слабшій формі: OpenAPI-спеки, protobuf-схеми, JSON-приклади,
контракти даних, руни, «племінне знання». Режим відмови однаковий, коли ці артефакти не забезпечені тестами і runtime-перевірками.

Якщо ваш опис інтерфейсу не тестується — це не спек, це казка на ніч.

Чому огляди коду не ловлять це надійно

Люди люблять питати: «Як рев’ю коду могло це пропустити?» Бо рев’ю — це процес вибіркової перевірки людиною, а не формальна доведення.
Невідповідності одиниць — це семантична помилка. Код може виглядати цілком розумним. Рев’ювер може не мати повного контексту,
не знати про одиниці з джерела, не помітити, що змінна з ім’ям impulse мала б бути impulse_ns.

Рев’ю допомагає, але не забезпечує виконання. Забезпечення приходить від:

  • типізованих одиниць або явних найменувань
  • тестів контрактів між виробником і споживачем
  • runtime-утверджень і валідації
  • end-to-end симуляцій в реалістичних умовах
  • саніті-чеків телеметрії з тривогами, налаштованими на зсув, а не лише на сплески

Як це виглядає в продукційних системах

Якщо ви керуєте сховищем, флотом або платформою даних, ви вже бачили версію MCO. Невідповідність одиниць стає:

  • SLO затримки вимірюється в мілісекундах, але дашборд малює секунди
  • обмежувач запитів налаштований у requests/minute, а клієнт очікує requests/second
  • збереження резервних копій «30» інтерпретується одним завданням як дні, іншим — як години
  • система зберігання віддає «GB» (десяткові), а фінанси очікують «GiB» (бінарні)

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

Жарт №2: Найшвидший спосіб зменшити витрати в хмарі — випадково трактувати мілісекунди як секунди — поки фінансовий директор не прочитає ваш звіт про інцидент.

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

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

Перший етап: доведіть контракт на межі

  1. Захопіть реальний payload (телеметричний кадр, API-запит/відповідь, запис файлу) з продакшну.
  2. Порівняйте його зі спеком (схема, ICD, protobuf-визначення, очікування одиниць).
  3. Перевірте одиниці та системи відліку: часову базу, систему координат, коефіцієнти масштабування, стиснення.

Якщо payload не самодескрибує одиниці, вважайте, що ви вже в зоні ризику.

Другий етап: шукайте зсув, а не лише сплески

  1. Побудуйте графіки залишків (прогноз проти спостереження) з часом.
  2. Шукайте монотонний дрейф або послідовний зсув після деплойменту чи зміни конфігурації.
  3. Порівняйте кілька незалежних сигналів, якщо можливо (наприклад, навігаційна оцінка проти сирого сенсора, метрика API проти сирого логу).

Третій етап: відтворіть на «відомо правильному» вхідному наборі

  1. Знайдіть фікстуру: історичний payload, золотий набір даних, записану послідовність маневрів.
  2. Проганяйте конвеєр/компонент end-to-end.
  3. Підтвердіть, що вихід відповідає очікуваному в межах толерантності.

Якщо ви не можете визначити «очікуване», ваша система не тестована, і надійність перетворюється на азартну гру.

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

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

Завдання 1: Знайдіть місця, де одиниці замовчуються (grep підозрілих полів)

cr0x@server:~$ rg -n "ms|millis|seconds|secs|ns|newton|lbf|pound|unit|scale|multiplier|conversion" /etc /opt/app/config
/opt/app/config/telemetry.yaml:41:  impulse_scale: 1.0
/opt/app/config/telemetry.yaml:42:  impulse_unit: "lbf*s"

Що це означає: У конфігурації явно вказано lbf·s. Добре — принаймні це задокументовано.

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

Завдання 2: Перевірте розбіжність версій сервісу в кластері

cr0x@server:~$ ansible -i inventory all -m shell -a 'myservice --version'
host-a | CHANGED | rc=0 >>
myservice 2.3.1
host-b | CHANGED | rc=0 >>
myservice 2.2.9

Що це означає: Змішані версії. Якщо обробка одиниць змінилася між версіями, у вас може бути спліт-брейн семантики.

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

Завдання 3: Перевірте OpenAPI/protobuf/схему на явні анотації одиниць

cr0x@server:~$ jq '.components.schemas.Telemetry.properties.impulse' openapi.json
{
  "type": "number",
  "description": "Thruster impulse",
  "example": 12.5
}

Що це означає: «Impulse» без одиниць — пастка. Приклад не допомагає.

Рішення: Оновіть описи схеми, щоб включити одиниці; додайте розширення x-unit або перейменуйте поля (наприклад, impulse_ns).

Завдання 4: Валідованість payload проти схеми (ловить відсутні/зайві поля)

cr0x@server:~$ python3 -m jsonschema -i sample_payload.json telemetry_schema.json
sample_payload.json validated successfully

Що це означає: Структурна форма в порядку. Це не перевіряє семантику, як одиниці.

Рішення: Додайте семантичну валідацію: допустимі діапазони і теги одиниць; розгляньте відхилення payload, що не мають метаданих одиниць.

Завдання 5: Помітити дрейф коефіцієнта масштабування в часовому ряді (швидко і брудно)

cr0x@server:~$ awk '{sum+=$2; n++} END{print "avg=",sum/n}' impulse_residuals.txt
avg= 4.43

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

Рішення: Порівняйте величину зсуву з відомими коефіцієнтами перетворення (наприклад, ~4.448 для lbf→N). Якщо співпадає — припиніть гадати й досліджуйте межу одиниць.

Завдання 6: Перевірте NTP/синхронізацію часу (зсуви часу імітують баги одиниць)

cr0x@server:~$ timedatectl
               Local time: Wed 2026-01-22 10:48:11 UTC
           Universal time: Wed 2026-01-22 10:48:11 UTC
                 RTC time: Wed 2026-01-22 10:48:11
                Time zone: UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Що це означає: Час у порядку й у UTC. Добре. Невідповідності одиниць часто ховаються за дрейфом часу.

Рішення: Якщо не синхронізовано — виправте час спершу; інакше ви неправильно діагностуєте причинно-наслідкові зв’язки.

Завдання 7: Підтвердіть часові мітки й одиниці у структурованих логах

cr0x@server:~$ jq -r '.ts,.duration_ms' /var/log/myservice/events.json | head
2026-01-22T10:46:01.120Z
250
2026-01-22T10:46:01.421Z
280

Що це означає: Імена полів тривалості включають «_ms». Це маленьке диво.

Рішення: Примусьте конвенції суфіксів (_ms, _bytes, _ns) через лінтер і чек-лист рев’ю коду.

Завдання 8: Простежте API виклик end-to-end, щоб побачити, де відбувається трансляція одиниць

cr0x@server:~$ curl -sS -H 'X-Request-ID: diag-123' http://localhost:8080/telemetry | jq .
{
  "impulse": 12.5,
  "impulse_unit": "lbf*s",
  "burn_duration_ms": 250
}

Що це означає: Payload самодескрибує одиниці для impulse, але не обов’язково для всього решти; тривалість явна.

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

Завдання 9: Підтвердіть, яка конфігурація жива (уникайте «виправлено в git, зламано в prod»)

cr0x@server:~$ systemctl show myservice -p FragmentPath -p Environment
FragmentPath=/etc/systemd/system/myservice.service
Environment=UNIT_SYSTEM=imperial IMPULSE_UNIT=lbf*s

Що це означає: Сервіс явно налаштовано на імперську систему через оточення. Це може перекрити поведінку за замовчуванням у коді.

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

Завдання 10: Визначте останні зміни (одиниці ламаються на краях змін)

cr0x@server:~$ git log -n 5 --oneline -- config/telemetry.yaml
a91b2cf set impulse scale
71c0d1a rename burn_duration to burn_duration_ms
9f10bb2 initial telemetry config

Що це означає: Хтось нещодавно чіпав масштабування імпульсу. Це ваш основний підозрюваний.

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

Завдання 11: Перевірте запити метрик на плутанину одиниць (приклад Prometheus)

cr0x@server:~$ promtool query instant http://localhost:9090 'rate(myservice_impulse_total[5m])'
myservice_impulse_total{job="myservice"} 0.21

Що це означає: Повернута швидкість, але невідомо в яких одиницях. Це N·s за секунду? lbf·s за секунду? Просто «імпульси»?

Рішення: Кодовуйте одиниці в іменах метрик та HELP-тексті; наприклад, myservice_impulse_newton_seconds_total й відхиляйте неоднозначні метрики під час рев’ю.

Завдання 12: Перевірте одиниці осей панелей (перевірка Grafana JSON)

cr0x@server:~$ jq -r '.panels[] | select(.title=="Burn duration") | .fieldConfig.defaults.unit' dashboard.json
ms

Що це означає: Панель явно використовує мілісекунди. Це перешкоджає оператору інтуїтивно прочитати «250» як «250 секунд».

Рішення: Стандартизувати одиниці панелей; примусити через CI в експорті JSON дашбордів.

Завдання 13: Виявити тихі зміни масштабування в бінарниках (strings може видати)

cr0x@server:~$ strings /usr/local/bin/myservice | rg -n "lbf|newton|N\\*s|unit"
10233:IMPULSE_UNIT
10234:lbf*s

Що це означає: У бінарнику є рядок за замовчуванням одиниці. Якщо конфіг відсутній, цей дефолт може мовчки застосовуватися.

Рішення: Замініть дефолти на поведінку «треба вказати» для конфігів з одиницями; зазнавайте невдачі на старті.

Завдання 14: Підтвердіть, що pipeline інжесту зберігає поля одиниць

cr0x@server:~$ jq -r '.impulse_unit' raw_event.json enriched_event.json
lbf*s
null

Що це означає: Збагачення видалило impulse_unit. Тепер аналітика нижнього рівня буде здогадуватися.

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

Три корпоративні міні-історії (досить реальні, щоб боліти)

Міні-історія 1: Інцидент, викликаний хибним припущенням

Платформа платежів мала два сервіси: один обчислював «ризик-скор», інший застосовував правила проти шахрайства. Сервіс
скорингу емінтував поле score як ціле від 0 до 1000. Сервіс застосування думав, що це від 0.0 до 1.0.
Обидві команди мали документацію. Обидві вірили, що інша команда її прочитала. Жодна команда не мала тесту контракту.

Інтеграція проходила базові тести, бо в прикладах були малі значення, а пороги правил були налаштовані вільно на ранньому розгортанні.
Потім випустили нову модель із більшим розкидом. Раптом enforcement почав відхиляти чисту частину трафіку. Не весь трафік.
Достатньо, щоб засвітити підтримку клієнтів і зіпсувати інакше спокійний тиждень.

On-call спочатку ганявся за затримками і помилками бази даних, бо симптом виглядав як повторні спроби й таймаути: клієнти переподавали платежі.
Платформа не падала; вона просто частіше відповідала «ні», що в операціях гірше, бо виглядає як політичне рішення, а не технічна відмова.

Виправлення було смішно простим: перейменувати поле на score_milli (0–1000), додати score_unit і додати інтеграційний тест,
який фейлить, якщо у «ratio» API приходить score > 1.0. Справжнє виправлення — культурне: змусити обидві команди ставитися до контрактів даних як до коду, а не до вікі-сторінки.

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

Команда сховища оптимізувала pipeline інжесту, перейшовши з JSON на компактний бінарний формат і «зекономивши байти», прибравши описові поля.
Серед видалених полів було block_size і його одиниця. Всі «знали», що це байти.
Окрім команди-споживача, яка історично трактувала це як кілобайти, бо UI підписував «KB».

Перший симптом проблеми був тонкий: дашборди показували повільну, постійну зміну «середнього розміру блоку». Жодної тривоги.
Платформа працювала. Але планування ємності почало дрейфувати. Організація почала купувати обладнання зарано в одному регіоні й запізно в іншому.
Така невідповідність — як отримати виклик опів на третю від фінансів.

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

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

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

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

Потім оновлення вендора змінило налаштування стиснення за замовчуванням. Пропускна здатність відновлення впала, а тривалість зросла, але
задача все одно повертала код виходу 0. Ніхто б не помітив цього, доки не знадобилося відновлення — і тоді було б уже пізно переговорювати RTO з реальністю.

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

«Нудна практика» не була в логуванні. Вона була в дисципліні робити тест відновлення сигналом першого класу для продакшну.
Так виживають: не героїками, а звичками, які припускають, що система буде вам брехати, якщо її не змусити цього не робити.

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

1) «Все в межах лімітів» — поки не стане ні

Симптоми: Повільний дрейф залишків, поступове погіршення точності, періодичні «мокрі місця».

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

Виправлення: Додайте виявлення зміщення: ковзні середні тривоги, CUSUM-подібні перевірки і явну валідацію перетворень при інжесті.

2) Впевнені дашборди, але неправильна вісь

Симптоми: Оператори клянуться, що система в нормі, бо графіки виглядають нормально; інциденти «несподівані».

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

Виправлення: Примусьте одиниці в дашбордах через CI; зобов’яжіть суфікси одиниць в іменах метрик; додавайте «unit»-лійбли там, де це підтримується.

3) Документація інтерфейсу є, але реальність відхилилася

Симптоми: Команди-виробник і команда-споживач обидві цитують документацію; інтеграція все одно ламається.

Корінь: Документи не прив’язані до автоматичних тестів; зміни контракту виходять без валідації споживачем.

Виправлення: Реалізуйте consumer-driven contract tests; трактуйте зміни схеми як версійні випуски з воротами сумісності.

4) «Ми просто переведемо пізніше»

Симптоми: Логіка конвертації дубльована по сервісах; несумісні виходи; баги важко відтворити.

Корінь: Відсутнє єдине джерело істини для конверсій; ad-hoc код перетворення в кожному клієнті.

Виправлення: Централізуйте перетворення в одній добре протестованій бібліотеці; забороніть локальні конверсії без вагомого виправдання і рев’ю.

5) Тихе видалення семантичних полів у конвеєрах

Симптоми: Звіти нижнього рівня розходяться після «оптимізації продуктивності»; поля одиниць стають null.

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

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

6) Змішані семантики версій під час розгортання

Симптоми: Лише деякі вузли поводяться «не так»; інцидент виглядає випадковим; канарка здається нормальною, але флот ні.

Корінь: Обробка одиниць змінилася в новій версії; часткове розгортання спричиняє неконсистентну інтерпретацію.

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

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

Покроковий план: зробіть так, щоб невідповідності одиниць було важко відправити

  1. Зробіть інвентар полів з одиницями у API, телеметрії, логах, метриках та конфігураціях. Якщо це число — припускайте, що воно має одиниці.
  2. Перейменовуйте поля з включенням одиниць, де можливо: duration_ms, size_bytes, temp_c.
  3. Додавайте метадані одиниць, коли перейменування неможливе: поля unit або анотації схеми.
  4. Реалізуйте тести контрактів між виробниками і споживачами; блокуйте мерджі при невідповідності.
  5. Розбивайте на старті, якщо конфіг одиниць відсутній або невідомий; дефолти — спосіб, яким неоднозначність виживає.
  6. Визначте дозволені діапазони (min/max) для ключових сигналів; невідповідності одиниць часто дають значення поза фізичною/бізнес-правдоподібністю.
  7. Оповіщення, що враховують зсув: ковзні середні, детектори дрейфу, відстеження залишків.
  8. Золоті фікстури: відомі входи з відомими виходами, включаючи конверсії (наприклад, lbf→N).
  9. Проганяйте end-to-end симуляції на реалістичних payload; синтетичні «щасливі шляхи» — місце, де баги ховаються.
  10. Операційні огляди: кожна зміна on-call включає «які припущення ми робимо щодо одиниць/часу/фреймів?»

Передпольотний чекліст інтеграції (використовуйте серйозно)

  • Усі числові поля мають явні одиниці в імені, схемі або метаданих.
  • Усі перетворення відбуваються в одній бібліотеці/модулі, а не розкидані.
  • Кожен інтерфейс має тест сумісності в CI.
  • Дашборди вказують одиниці осей і не покладаються на значення за замовчуванням.
  • Оповіщення включають детектори дрейфу/зсуву, а не лише пороги.
  • План розгортання враховує поведінку при змішаних версіях.
  • Рунбук містить «невідповідність одиниць» як першу гіпотезу.

FAQ

1) Чи Mars Climate Orbiter загинув виключно через одну помилку конвертації одиниць?

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

2) Чому цього не помітили раніше?

Бо помилка проявлялася як поступовий зсув, а не явна відмова. Дрейф легко раціоналізувати як шум,
особливо під тиском графіка й коли система «виглядає стабільною».

3) Який сучасний еквівалент lbf·s vs N·s у хмарних системах?

Секунди проти мілісекунд, байти проти MiB, UTC проти місцевого часу і одиниці швидкості (за секунду проти за хвилину) —
основні приклади. Також «відсотки» проти «частки» (0–100 проти 0–1) часто з’являються в ML і ризикових системах.

4) Чи треба зберігати одиниці з кожною точкою даних?

Не обов’язково в кожному записі. Можна зберігати одиниці в схемах, реєстрах або версійному метаданому — за умови,
що споживачі не зможуть випадково їх ігнорувати і все одно продовжувати роботу.

5) Хіба найменування полів суфіксами як _ms не потворне?

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

6) Який найкращий спосіб запобігти багам одиниць у коді?

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

7) Як виявити невідповідність одиниць під час інциденту?

Шукайте послідовні відношення в помилках (наприклад, ~1000×, ~60×, ~4.448×). Порівнюйте незалежні вимірювання.
Валідовайте сирий payload на межі і шукайте втрачені поля метаданих.

8) Що робити, якщо дві команди не погоджуються щодо одиниць і обидві «мають документацію»?

Документація — не авторитет; продакшн — авторитет. Захопіть реальні payload, напишіть провальний тест контракту, що кодує правильні очікування одиниць,
і версіонуйте інтерфейс, щоб неоднозначність не повернулась мовчки.

9) Чому такі відмови продовжують траплятись навіть у зрілих організаціях?

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

Висновок: наступні кроки, які можна зробити цього тижня

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

Практичні кроки:

  1. Виберіть ваші топ-10 числових полів у телеметрії або API й зробіть одиниці явними (в імені або метаданих).
  2. Додайте один тест контракту, який фейлить при невідповідності одиниць — зробіть його блокером для мерджів.
  3. Додайте одне оповіщення на дрейф/зсув для ключової метрики залишків.
  4. Видаліть один дефолт системи одиниць і примусьте явну конфігурацію.
  5. Проведіть game day, де інжектований збій — «змінили одиниці наверху». Дивіться, скільки часу займуть пошуки.

Якщо робите лише одне: ставте семантику інтерфейсів у ранг критичних для продакшну. Бо вона є. Космічний апарат не дбає,
що ваша організаційна діаграма складна.

← Попередня
Ubuntu 24.04 «Failed to start …»: найшвидший робочий процес діагностики systemd (випадок №2)
Наступна →
ZFS secondarycache: коли L2ARC не повинен кешувати нічого

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