Помилка ракети Patriot: коли зсув часу став проблемою на полі бою

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

Час — це залежність. Ставтеся до нього як до підсистеми, або він поставить вас у кут як аматора. Якщо ви коли-небудь ганялися за «випадковим» стрибком затримки,
непевною помилкою TLS або переключенням бази даних, яке «не повинно було статися», ви вже знайомі зі зсувом часу в неформальному чаті після роботи.

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

Що сталося (і чому «дрібні помилки» — не дрібні)

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

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

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

Цитата, яка має бути на дошці інцидентів

«Сподівання — не стратегія.» — перефразована ідея, часто цитована в колах надійності/операцій

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

Історичні факти, що важливі для інженерів

  • Patriot спочатку проектувався для протиповітряної оборони, а потім адаптувався для протиракетної оборони. Операційне середовище змінилося швидше, ніж культура розробки ПЗ.
  • Відмова сталася в 1991 році під час війни в затоці, за реальних бойових умов із тривалими операціями та високими ставками.
  • Ключова проблема стосувалась перетворення часу: конвертація лічильника (тиків) у секунди з використанням фіксованої точки та округленої константи.
  • Округлення було крихітним за одиничну операцію, але накопичувалось із часом роботи. Дрібні похибки на події стають великими на довгих горизонтах.
  • Відстеження системи використовувало прогнозування, тобто помилка часу перетворюється на помилку позиції. Прогноз підсилює помилки таймінгу.
  • Триваліша, ніж очікувалося, безперервна робота збільшила зсув понад допустимий поріг. Система «працювала нормально», поки раптом ні.
  • Нібито існував оновлення ПЗ для пом’якшення проблеми, але впровадження змін під час війни складне, повільне та інколи політично делікатне.
  • Інцидент став навчальним кейсом у курсах з інженерії програмного забезпечення про числову точність, дрейф вимог та операційні припущення.

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

Механіка помилки: фіксована точка, округлення та накопичений зсув

Поговоримо про механіку без того, щоб перетворювати це на семінар з чисельного аналізу.
Система Patriot використовувала внутрішній годинник, що рахував десяті секунди (або подібну одиницю тік‑базованого лічильника; важливо те, що це лічильник, а не плаваючий годинник).
Щоб прогнозувати місцезнаходження цілі, ПЗ потребувало часу в секундах.

Конвертація тік → секунда концептуально проста:

  • ticks = integer counter
  • seconds = ticks × 0.1

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

Фіксована точка: компроміс embedded-інженера

У ресурсно-обмежених системах (історично особливо) плаваюча точка могла бути дорогою або недоступною, тому інженери використовують фіксовану точку:
представляють дійсні числа як цілі з умовним масштабом. Приклад: зберігати секунди в одиницях 2^-N або зберігати «0.1» як ціле відношення.

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

Специфічна форма помилки: «помилка за тік» × «тіки з моменту запуску»

Зсув поводиться так:

  • Ви апроксимуєте коефіцієнт перетворення (наприклад, 0.1 секунди за тік) з обмеженою точністю.
  • Кожне перетворення вносить крихітну похибку (часто частину тіка).
  • За багато тиків ця дробова похибка накопичується в помітний часовий зсув.
  • Часовий зсув перетворюється на зсув позиції через швидкість: position_error ≈ velocity × time_error.

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

Чому ця помилка виживає в тестуванні

Це не через те, що інженери дурні. Це тому, що тестування зазвичай обмежене:

  • Короткі тестові прогони не накопичують достатньо зсуву.
  • Умови лабораторії не відповідають експлуатаційним режимам.
  • Критерії прийнятності фокусуються на «працює зараз», а не «працює після 100 годин».
  • Час часто підмінюють у тестах, що необхідно, але це може приховати інтеграційні реальності.

Баги довготривалого часу потребують довготривалого тестування або хоча б формального обґрунтування і моніторингу, який явно враховує накопичення.
Якщо ви не можете запускати тест 100 годин, симулюйте 100 годин за допомогою прискорених лічильників і перевірте математику в масштабі.

Як зсув перетворився на пропуск перехоплення

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

Певна кількість помилки припустима; фільтри відстеження можуть поглинати шум. Але накопичений зсув часу — це не «шум».
Це зміщення. Зміщення постійно штовхає вас у неправильному напрямку.

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

  1. Нормальна робота: система працює, зсув повільно зростає, ніхто не помічає.
  2. Наближення до відмови: якість трасування поступово погіршується; система частіше губить або неправильно асоціює треки.
  3. Критичний момент: з’являється швидка ціль; вікно прогнозу вузьке; зміщення важливе; система не встигає правильно зорієнтувати трек для ураження.

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

Жарт №1: Зсув часу — єдина помилка, що стає гіршою, поки ви спите, що грубо, бо в цей час ви не пейджите себе.

Справжні уроки (для SRE, embedded-інженерів і менеджерів)

1) Тривалість роботи — не чеснота сама по собі

Культура «п’ять дев’яток» інколи перетворюється на «нічого не перезавантажувати». Це релігія, а не інженерія.
Тривала робота підвищує ризик витоків, перевертання лічильників, повільної втрати точності і дивних станів, що з’являються лише після днів безперервної роботи.

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

2) Дрейф вимог — реальне джерело багів

Patriot був адаптований до нового загрозового середовища. Це не рідкість. Нерідко люди вірять, що початкові припущення досі в силі.
У корпоративних системах «дрейф вимог» часто маскується під «просто конфіг змінився» або «просто збільште таймаут».

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

3) Час — це залежність розподілених систем навіть на одній машині

Навіть на одному вузлі є кілька годинників:

  • Wall clock (CLOCK_REALTIME): може скакати через корекції NTP або ручні зміни.
  • Monotonic clock (CLOCK_MONOTONIC): стабільний, але не прив’язаний до цивільного часу.
  • TSC/HPET апаратні реалії: дрейф, масштабування частоти, артефакти віртуалізації.

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

4) Точність має бути явним проєктним рішенням

Якщо ваша система використовує фіксовану точку, документуйте:

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

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

5) Моніторинг має включати якість часу, а не тільки його значення

Багато дашбордів показують «NTP увімкнено» як прапорець у формі відповідності. Непотрібно.
Потрібно: offset, корекція частоти, jitter і досяжність. І алерти, що спрацьовують до того, як ваш зсув стане операційно значущим.

Швидкий план діагностики: зсув часу й проблеми з таймінгом

Коли щось пахне «таймінгом» (періодичні помилки автентифікації, дивні інвалідації кешу, флапання розподілених блокувань, телеметрія поза порядком),
не блукайте. Запустіть чітку послідовність перевірок.

По-перше: перевірте, чи ви не обманюєте себе щодо поточного часу

  1. Перевірте стан локального годинника: синхронізований він? Чи NTP/chrony дійсно його контролює?
  2. Перевірте величину зсуву: ви відстаєте на мілісекунди, секунди, хвилини?
  3. Перевірте події кроку: чи годинник не скакав недавно?

По-друге: підтвердіть джерело часу та мережевий шлях

  1. Хто сервер часу? Локальний, корпоративні stratum-сервери чи публічні джерела?
  2. Чи доступний UDP/123? Або ви «синхронізуєтесь» з нікчемою?
  3. Чи гіпервізор не втручається? Віртуальний час може бути «креативним».

По-третє: зіставте симптоми з типом годинника

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

По-четверте: обмежте зону ураження

  1. Зупиніть втрату: зафіксуйте інстанси, тимчасово відключіть «жорсткі» перевірки часу, якщо це безпечно (наприклад, розширте допустимий дисбаланс), поки не відновите синхронізацію.
  2. Зменшіть накопичення стану: перезапустіть сервіси з відомими чутливими до часу кешами за потреби.
  3. Запобігти повторенню: виправте корінну причину, а потім додайте алерти на offset/jitter/doсяжність.

Жарт №2: Якщо ви коли-небудь знайдете «часоутримання» у розділі «нефункціональні вимоги», вітаю — ви відкрили функціональну вимогу з кращим маркетингом.

Практичні завдання з командами: виявити, кількісно оцінити і вирішити

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

Task 1: Check if the system thinks it’s synchronized

cr0x@server:~$ timedatectl
               Local time: Mon 2026-01-22 14:10:03 UTC
           Universal time: Mon 2026-01-22 14:10:03 UTC
                 RTC time: Mon 2026-01-22 14:10:02
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Що це означає: «System clock synchronized: yes» вказує, що служба синхронізації дисциплінує годинник.

Рішення: Якщо там написано no, вважайте час підозрілим і переходьте до перевірок NTP/chrony перш ніж дебажити щось інше.

Task 2: Identify whether chrony is healthy (offset, jitter, stratum)

cr0x@server:~$ chronyc tracking
Reference ID    : 192.0.2.10 (ntp-a.internal)
Stratum         : 3
Ref time (UTC)  : Mon Jan 22 14:09:55 2026
System time     : 0.000021345 seconds slow of NTP time
Last offset     : -0.000012311 seconds
RMS offset      : 0.000034112 seconds
Frequency       : 12.345 ppm fast
Residual freq   : -0.021 ppm
Skew            : 0.120 ppm
Root delay      : 0.003210 seconds
Root dispersion : 0.001102 seconds
Update interval : 64.0 seconds
Leap status     : Normal

Що це означає: Зсув ≈21µs повільніше; це відмінно. Корекція частоти мала, jitter низький.

Рішення: Якщо ви бачите зсуви в мілісекундах/секундах або «Leap status: Not synchronised», припиніть і виправте синхронізацію часу перш за все.

Task 3: See which NTP sources are reachable and preferred

cr0x@server:~$ chronyc sources -v
210 Number of sources = 3

  .-- Source mode  '^' = server, '=' = peer, '#' = local clock.
 / .- Source state '*' = current best, '+' = combined, '-' = not combined,
| /   '?' = unreachable, 'x' = time may be in error, '~' = time too variable.
||                                                 .- xxxx [ yyyy ] +/- zzzz
||      Reachability register (octal) -.           |  xxxx = adjusted offset,
||      Log2(Polling interval) --.      |          |  yyyy = measured offset,
||                                \     |          |  zzzz = estimated error.
||                                 |    |           \
^* ntp-a.internal                  377   6   -21us[ -33us] +/-  312us
^+ ntp-b.internal                  377   6   -10us[ -18us] +/-  401us
^? ntp-c.internal                    0   6     +0ns[  +0ns] +/-    0ns

Що це означає: Два здорові джерела; одне недоступне (^?, reach 0).

Рішення: Якщо більшість джерел недоступні, перевірте firewall/маршрутизацію. Якщо «best» джерело часто міняється, підозрівайте мережевий jitter або поганий сервер.

Task 4: Confirm UDP/123 is reachable to your time server

cr0x@server:~$ nc -uvz ntp-a.internal 123
Connection to ntp-a.internal 123 port [udp/ntp] succeeded!

Що це означає: Основна досяжність є. Це не доказ гарного часу, але швидко виключає «заблокований NTP».

Рішення: Якщо команда невдала, координуйтеся з мережею/безпекою. Не «латайте» тимчасово, відключаючи валідацію часу в додатках як постійне рішення.

Task 5: Verify kernel time discipline status

cr0x@server:~$ timedatectl timesync-status
       Server: 192.0.2.10 (ntp-a.internal)
Poll interval: 1min 4s (min: 32s; max 34min 8s)
         Leap: normal
      Version: 4
      Stratum: 3
    Reference: 9B1A2C3D
    Precision: 1us (-24)
Root distance: 1.5ms
       Offset: -17us
        Delay: 310us
       Jitter: 52us
 Packet count: 128
    Frequency: +12.345ppm

Що це означає: Offset і root distance малі; система дисциплінована.

Рішення: Якщо root distance велика (сотні мс+), якість часу погана навіть якщо «synchronized: yes». Розгляньте кращі джерела або ближчих stratum-серверів.

Task 6: Detect whether the clock stepped (jumped) recently

cr0x@server:~$ journalctl -u chrony --since "2 hours ago" | tail -n 10
Jan 22 13:02:11 server chronyd[612]: Selected source 192.0.2.10
Jan 22 13:02:11 server chronyd[612]: System clock wrong by -0.742314 seconds
Jan 22 13:02:11 server chronyd[612]: System clock was stepped by -0.742314 seconds
Jan 22 13:02:12 server chronyd[612]: Frequency 12.345 ppm
Jan 22 13:03:16 server chronyd[612]: Source 192.0.2.11 replaced with 192.0.2.12

Що це означає: Відбувся крок ≈742ms. Це може зламати системи, що припускають, що час ніколи не відкатується або не скаче.

Рішення: Якщо бачите кроки — знайдіть причину: холодний старт, suspend/resume віртуалки або втрата синхронізації. Розгляньте конфігурацію slew-only для чутливих додатків (обережно).

Task 7: Check for VM or host time anomalies (dmesg clues)

cr0x@server:~$ dmesg | egrep -i "clocksource|tsc|timekeeping" | tail -n 8
[    0.000000] tsc: Detected 2294.687 MHz processor
[    0.000000] clocksource: tsc-early: mask: 0xffffffffffffffff max_cycles: 0x211f0b6d85a, max_idle_ns: 440795223908 ns
[    0.125432] clocksource: Switched to clocksource tsc
[  831.441100] timekeeping: Marking clocksource 'tsc' as unstable because the skew is too large
[  831.441105] clocksource: Switched to clocksource hpet

Що це означає: Ядро помітило нестабільний TSC і перемкнуло джерело годинника. Це може корелювати з дрейфом і дивною поведінкою таймінгу, особливо в VM.

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

Task 8: Compare time between hosts (quick skew check)

cr0x@server:~$ for h in app01 app02 db01; do echo -n "$h "; ssh $h "date -u +%s.%N"; done
app01 1769091003.123456789
app02 1769091003.123991234
db01  1769091002.997000111

Що це означає: db01 відстає ≈126ms від app01. Це достатньо, щоб порушити жорсткі перевірки skew і перевпорядкувати події.

Рішення: Якщо skew > ваша толерантність системи (зазвичай 50–200ms залежно від протоколу), виправте синхронізацію часу перед тим, як дебажити «таємничі» помилки додатків.

Task 9: Verify monotonic clock behavior (no backwards jumps)

cr0x@server:~$ python3 - <<'PY'
import time
a=time.monotonic()
time.sleep(0.2)
b=time.monotonic()
print("delta_ms", (b-a)*1000)
PY
delta_ms 200.312614

Що це означає: Монотонний час зростає стійко. Якщо ваш додаток використовує wall clock для інтервалів, він може зламатися на стрибках; монотонний уникає цього класу проблем.

Рішення: Якщо ви знайдете код, що використовує wall clock для інтервалів, заплануйте виправлення. Це не опція; це майбутній інцидент.

Task 10: Quantify drift rate (ppm) over time using chrony

cr0x@server:~$ chronyc sourcestats -v
210 Number of sources = 2
  Name/IP Address            NP  NR  Span  Frequency  Freq Skew  Offset  Std Dev
  ntp-a.internal             20  12   18m     +12.345     0.120   -21us     52us
  ntp-b.internal             18  10   18m     +11.998     0.200   -10us     60us

Що це означає: Корекція частоти ≈12 ppm швидше. Це нормально для звичайних годинників; chrony компенсує.

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

Task 11: Inspect whether NTP is configured to use a local “lies-to-you” source

cr0x@server:~$ grep -R "server\|pool\|local" /etc/chrony/chrony.conf
server ntp-a.internal iburst
server ntp-b.internal iburst
# local stratum 10

Що це означає: Налаштовані реальні сервери; fallback на «local clock» закоментований. Добре.

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

Task 12: Detect log timestamp anomalies (time went backwards)

cr0x@server:~$ journalctl --since "1 hour ago" | awk '
  $1 ~ /^[A-Z][a-z]{2}$/ {
    ts=$1" "$2" "$3;
    if (prev != "" && ts < prev) { print "time went backwards:", prev, "->", ts }
    prev=ts
  }' | head

Що це означає: Якщо з’явиться вивід — у вас аномалії порядку (часто через кроки годинника або змішування логів з різних хостів).

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

Task 13: Validate TLS failures might be clock-related (certificate validity)

cr0x@server:~$ openssl x509 -in /etc/ssl/certs/ca-certificates.crt -noout -dates 2>/dev/null | head -n 2
notBefore=Jan  1 00:00:00 2025 GMT
notAfter=Dec 31 23:59:59 2030 GMT

Що це означає: Дати сертифікатів в порядку, але якщо системний годинник позаду notBefore, TLS‑handshake помиляється так, ніби проблема мережі.

Рішення: Якщо раптові TLS‑помилки на підмножині хостів — перевірте зсув часу перш ніж панікувати і перевипускати сертифікати.

Task 14: Confirm application containers inherit sane time (host vs container)

cr0x@server:~$ docker exec -it api-1 date -u
Mon Jan 22 14:10:05 UTC 2026

Що це означає: Контейнери зазвичай використовують час хоста; якщо хост помилковий, усі контейнери помилкові синхронно.

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

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

Міні-історія 1: Хибне припущення («час достатньо стабільний»)

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

Під час мережевого обслуговування підмножина нод втратила доступ до внутрішніх NTP‑серверів. Chrony продовжував працювати, але тепер годинники пішли у вільний режим.
Протягом кількох годин ці ноди зсунути. Не на хвилини — на сотні мілісекунд тут, секунда там. Дашборди все ще показували «сервіс здоровий».
Звісно: більшість дашбордів не вимірюють якість часу.

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

Це «виправлення» зробило гірше. Тепер дійсно застарілі події приймалися, що змінило семантику функцій проти шахрайства.
Вихід моделі змінився, і ніхто не міг пояснити чому. Інцидент закінчився, коли хтось перевірив offset chrony на дрейфуючих хостах і відновив доступ до NTP.

Корінна проблема — хибне припущення: що зсув часу буде незначним і що годинники «в основному правильні».
Реальне виправлення було таким:
(1) сигналізувати про offset/досяжність, і
(2) використовувати монотонний час для обчислення свіжості в межах хоста, а для крос‑хост упорядкування — ідентифікатори послідовності подій.

Міні-історія 2: Оптимізація, що повернулась проти (збереження CPU шляхом зниження точності)

Команда зберігання підтримувала high-throughput сервіс інґесту, який ставив кожному запису логічну часову мітку для упорядкування й рішень про компактування.
Під навантаженням профайли показали, що перетворення часу й форматування в гарячому шляху коштують CPU. Хтось запропонував охайну оптимізацію: замінити високоточну мітку
на дешевий тиковий лічильник і попередньо обчислений коефіцієнт конверсії в фіксованій точці. Це зекономило CPU і виглядало чисто.

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

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

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

Оптимізація дозволена. Невимірена, необмежена оптимізація — це шлях до повільної відмови, що виглядає як «ентропія».

Міні-історія 3: Нудна практика, що врятувала день (обслуговування + SLO часу)

Платформа охорони здоров’я експлуатувала флот API‑серверів і процесорів повідомлень у кількох ЦОДах.
Їхня команда надійності мала політику, яка виглядала надто консервативною: кожен вузол отримує запланований рестарт в межах визначеного інтервалу,
і кожне середовище має SLO якості часу (пороги offset, досяжності і jitter).

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

Однієї ночі зміни в мережі частково заблокували UDP/123 між сегментом і внутрішніми серверами часу.
Алерти time SLO спрацювали за кілька хвилин: зростаючий offset на підмножині нод, досяжність падає.
Оператор на виклику не мусив робити довгих висновків з симптомів; телеметрія явно вказувала на годинник.

Відповідь була нудною й ефективною:
перенаправити NTP, підтвердити збіжність offset, прокрутити уражені ноди рестартами, щоб очистити чутливий до часу стан, а потім перевірити системи вниз по ланцюжку (JWT, TLS, планувальники).
Клієнти майже не помітили. Звіт по інциденту був короткий. Найбільш суперечливим питанням було, хто має оформити заявку на зміну firewall.

Нудні практики часто просто «правильний компроміс», повторений стільки разів, що люди забувають, навіщо вони потрібні.
Тримайте політику. Документуйте причини. І не сперечайтеся з фізикою.

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

  • Симптом: Випадкові TLS‑помилки («certificate not yet valid») на підмножині хостів
    Корінна причина: годинник хоста позаду реального часу; NTP недоступний або крок назад після resume
    Виправлення: відновити досяжність NTP, перевірити offset через chronyc tracking, потім перезапустити клієнти, що кешують сесії, якщо потрібно
  • Симптом: Розподілені блокування флапають; безперервна переобрання лідерів
    Корінна причина: оренди на основі часу стінки; кроки годинника спричиняють закінчення або розширення оренд
    Виправлення: використовувати монотонний час для тривалості оренд; забезпечити синхронізацію годинника для метаданих із часовими мітками; сигналізувати про кроки годинника
  • Симптом: Метрики або логи поза порядком між хостами; трасування виглядає як спагеті
    Корінна причина: крос‑хостовий skew; один сегмент втратив NTP і пішов у вільний режим; pipeline інґесту довіряє часовим міткам без перевірки
    Виправлення: впровадити SLO синхронізації часу; додати логіку інґесту, що толерує обмежений skew; включити sequence ID або монотонне впорядкування в межах хоста
  • Симптом: Неправильна робота лімітів швидкості («раптом всі перевищили ліміт» або «ніхто не перевищує»)
    Корінна причина: лічильники, сформовані по часових бакетах на основі wall clock; стрибок часу змінює межі бакетів
    Виправлення: базувати бакети на монотонному часі або використовувати epoch, згенерований сервером з довіреного джерела; уникати вбудованого бочкового групування на wall clock
  • Симптом: Заплановані джоби запускаються двічі або не запускаються після корекції NTP
    Корінна причина: планувальник використовує wall clock і не обробляє стрибки; системний час зроблено step для виправлення зсуву
    Виправлення: налаштувати синхронізацію часу на slew, коли можливо; використовувати монотонні таймери; додати idempotency ключі для джобів
  • Симптом: «Працює дні, а потім деградує» в трекінгу, стримінгу чи контрольних петлях
    Корінна причина: накопичена похибка округлення, переповнення лічильника або дрейф, що взаємодіє з припущеннями про довгу роботу
    Виправлення: порахувати найгірший випадок похибки за максимальний час роботи; підвищити точність; безпечно скидати стан під час планового обслуговування
  • Симптом: Тільки VM дрейфують; bare metal в порядку
    Корінна причина: нестабільний TSC, перевантаження хоста, артефакти suspend/resume, погане налаштування паравіртуального годинника
    Виправлення: координуватися з командою віртуалізації; перевірити стабільність kernel clocksource; переконфігурувати chrony для VM‑середовищ

Чеклисти / покроковий план

Чеклист A: Запобігти помилці «форми Patriot» у власних системах

  1. Інвентаризуйте залежності від часу: токени автентифікації, кеші, планувальники, упорядкування, оренди, компактування, захист від повторного виконання.
  2. Оголосіть максимально підтримувану тривалість роботи для компонентів зі станом, що накопичується; тестуйте проти неї.
  3. Використовуйте монотонний час для інтервалів (таймаути, повтори, backoff, rate limiting), а wall time — для презентації/обміну.
  4. Визначте SLO якості часу: макс. offset, макс. jitter, мінімальна кількість досяжних джерел.
  5. Сигналізуйте про тренди досяжності і зсуву, а не тільки «NTP працює».
  6. Тестуйте поведінку при довгій роботі: прискорені лічильники, soak‑тести або формальні аналізи меж.
  7. Централізуйте конверсії часу: одна бібліотека, один масштаб, одна політика округлення, покрита тестами.
  8. Плануйте безпечні рестарти: вікна обслуговування, rolling restarts, відновлення стану, ідемпотентність.

Чеклист B: Під час реагування на інцидент при підозрі на час

  1. Перевірте offset зараз на уражених хостах (chronyc tracking / timedatectl).
  2. Перевірте досяжність джерел часу (chronyc sources -v, шлях UDP/123).
  3. Пошукайте кроки у логах (journalctl -u chrony).
  4. Порівняйте крос‑хостовий skew швидко (SSH‑цикл з date -u).
  5. Пом’якшіть зону ураження: тимчасово розширити толерантність до skew де безпечно, зафіксувати лідерів, призупинити переходи стану, чутливі до часу.
  6. Відновіть дисципліну часу: виправте мережу/політику, забезпечте кілька джерел, перевірте збіжність.
  7. Очистіть стан: перезапустіть сервіси з часовочутливими кешами; коректно переоберіть лідерів; повторіть невдалі джоби ідемпотентно.
  8. Закріпіть профілактику: додайте алерти часового SLO, вніс зміни в управління змінами для NTP/firewall, і додайте пост‑інцидентні тести.

Питання й відповіді

1) Чи була помилка Patriot «просто про плаваючу точку»?

Ні. Основна проблема — точність і накопичення. Фіксована точка з округленням може бути цілком прийнятною,
але потрібно оцінювати межу похибки за максимальною тривалістю роботи й сценаріями. Інцидент Patriot — класичний приклад «мала систематична похибка × довгий час = великий промах».

2) Чому 0.1 створює проблеми у двійковій формі?

Тому що багато десяткових дробів у двійковій представлені періодично, як 1/3 у десятковій. Якщо ви зберігаєте 0.1 кінцевою кількістю двійкових цифр, ви апроксимуєте її.
Апроксимація — це нормально; необліковане накопичення — ні.

3) Чи міг моніторинг це виявити?

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

4) Чи є перезавантаження дійсним пом’якшенням для багів накопичення часу?

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

5) Який зсув годинника «занадто великий» у корпоративних системах?

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

6) NTP проти chrony: чи важливо?

Обидва можуть працювати. Chrony часто вважають кращим на сучасних Linux, особливо в VM‑середовищах, бо він краще поводиться при змінних мережевих умовах.
Більше значення має не демон, а те, чи у вас:
(1) кілька адекватних джерел,
(2) надійна досяжність до них,
і (3) алерти на offset/jitter/doсяжність.

7) Чому б просто не використовувати GPS‑час скрізь?

GPS може бути відмінним джерелом, але він має власні режими відмов: проблеми з антеною, втрата сигналу, спуфінг/глушіння та операційна складність.
Багато організацій використовують GPS‑підтримувані stratum‑1 сервери внутрішньо, а потім розповсюджують час через NTP/PTP у контрольованих мережах.

8) Яка практична різниця між монотонним часом і wall clock?

Монотонний час слугує для вимірювання тривалостей; він не має відкатів при корекціях wall clock.
Wall clock відповідає на питання «який зараз час». Помилка у виборі призводить до завислих повторів, передчасно прострочених токенів або планувальників, що «подорожують у часі».

9) Якщо стрибки часу небезпечні, чи варто їх заборонити?

Не категорично. Стрибок може бути необхідним при завантаженні або коли зсув надто великий. Але ви маєте знати, які додатки ламаються при стрибках,
надавати перевагу slewing у робочому режимі і проектувати критичні компоненти з використанням монотонних інтервалів і ідемпотентної логіки.

10) Який операційний висновок з інциденту Patriot?

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

Наступні кроки, які ви справді можете виконати цього тижня

Якщо ви експлуатуєте production‑системи, інцидент Patriot — не лише історичний урок. Це нагадування, що час — частина вашої поверхні надійності.
Ось що зробити далі, по порядку, без героїзму.

  1. Додайте метрики якості часу у моніторинг: offset chrony, jitter, досяжність і події кроків.
  2. Встановіть явний бюджет skew для підсистем (автентифікація, упорядкування сховища, планувальники). Запишіть число.
  3. Аудитуйте код на предмет використання wall‑clock для інтервалів. Замініть на монотонні таймери там, де це можливо.
  4. Проведіть контрольований експеримент: заблокуйте NTP в сегменті staging і подивіться, що ламається. Виправте те, що ламається.
  5. Визначте максимальну безпечну тривалість роботи для компонентів із відомими ризиками накопичення; реалізуйте rolling maintenance restarts за потреби.
  6. Централізуйте конверсії часу і додайте тести на довгу роботу (прискорені лічильники), щоб регресії точності були виявлені до production.

Інцидент Patriot відомий через видимі і негайні наслідки. Більшість помилок таймінгу в бізнес‑системах тихіші:
кілька втрачених подій, кілька невірних рішень, тиждень «мабуть стало повільніше». Тихі відмови все одно коштують грошей і довіри — просто на виплатах.

← Попередня
Проблеми резервного копіювання/відновлення Proxmox LXC: помилки tar, права доступу та нюанси файлових систем
Наступна →
PostgreSQL vs ClickHouse: ETL-патерни, що не породжують хаос даних

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