Програмне забезпечення часто сприймає час як число, що передбачувано зростає. Production показує іншу реальність: годинники drift-ять, job запускаються пізно, повідомлення приходять не по порядку, retry дублює роботу, а сервіси не погоджуються щодо поточного моменту. Надійний time-based дизайн не припускає ідеального годинника. Він визначає, який clock є authoritative, яка невизначеність допустима й що відбувається, коли операція виконується раніше, пізніше або більше одного разу.

Використовуйте правильний clock для питання

Wall-clock time відповідає, коли подія відбулася у спільному світі. Monotonic time відповідає, скільки тривала операція на одній машині. Timeout, виміряний wall clock, може зламатися після synchronization correction. Audit event із monotonic value не має сенсу поза процесом.

Розділяйте ці API в коді. Deadline, утворений із duration, повинен використовувати monotonic source, а persisted event — задокументоване UTC representation.

Expiration є політикою, а не лише порівнянням

Перевірка now > expires_at здається простою, доки сервіси не мають clock skew або запит не переходить межу під час обробки. Security token може дозволяти малий skew, reservation потребує atomic database enforcement, а cache іноді коротко віддає stale value під час refresh.

Визначте inclusive чи exclusive boundary, authoritative clock і допустимий grace period. Централізоване правило не дозволить різним endpoint по-різному інтерпретувати один timestamp.

Запланована робота іноді запускається пізно

Scheduler може гарантувати, що job стає доступним у певний момент, але не негайне виконання. Машина перезапускається, queue накопичується, deployment зупиняє worker, а dependency недоступна. Job повинен знати, чи пізнє виконання ще корисне, чи його треба пропустити або компенсувати.

Recurring job також потребує missed-run policy. Виконання всіх пропущених occurrence після downtime може перевантажити систему; пропуск усіх може втратити важливу обробку. Рішення залежить від бізнес-вимоги.

Time-triggered operation має бути idempotent

Distributed scheduler і queue можуть доставити роботу більше одного разу. Timeout та retry трапляються навіть після успішного першого виконання. Стабільний operation ID, uniqueness constraint або recorded state не дозволяє повтору створити дубль invoice, email чи transfer.

Exactly-once execution важко гарантувати між системами. Безпечна повторюваність зазвичай надійніша за припущення, що scheduler ніколи не дублює роботу.

Event timestamp не гарантує порядок

Два сервіси можуть записати події з годинниками, що відрізняються на секунди. Network delay призводить до того, що рання подія приходить пізніше. Сортування лише за wall timestamp створює правдоподібну, але неправильну послідовність.

Для strict ordering потрібні sequence number, logical clock, database ordering або domain version. Timestamp залишається корисним для observation та приблизної хронології, але не повинен мовчки ставати concurrency control.

Передавайте час у бізнес-логіку як залежність

Код, який всюди викликає реальний clock, важко тестувати. Clock abstraction або explicit current instant дозволяє детерміновано перевіряти expiration boundary, future schedule та retry. Це також робить вибір між wall і monotonic time видимим.

Fake clock має рухатися за командою тесту, а не через sleep. Швидкі детерміновані tests заохочують покривати edge cases, де time-based система найчастіше ламається.

Critical boundary потребує transaction

Для reservation, lease або обмеженої пропозиції перевірка часу в application code та пізніше update створює race. Authoritative database transaction може одночасно порівняти deadline й змінити стан.

External side effect все одно потребує idempotency та reconciliation. Transaction захищає внутрішній стан, але не гарантує, що email provider або payment network виконає дію рівно один раз.

Reconciliation відновлює пропущені припущення

Навіть надійний scheduler потребує periodic job, що знаходить overdue records, stuck work і inconsistent states. Reconciliation перетворює тимчасовий збій на відновлювану затримку, а не постійну втрату. Воно має бути безпечним для повторного запуску.

Спроєктувати reconciliation спочатку часто простіше, ніж довести, що кожен distributed timing path ніколи не помилиться.

Зберігайте контекст для пояснення рішення

Коли система завершує доступ, запускає job або відхиляє запит, logs повинні містити relevant timestamps, clock source і policy result. Не записуйте лише formatted local date, яку не можна порівняти між сервісами.

Dashboard корисно розділяти scheduled time, enqueue time, start time і completion time. Так видно, де саме виникла затримка: у scheduler, queue або execution.

Проєктуйте з урахуванням невизначеності

Retry policy повинен знати про deadline

Повтор через годину може бути корисним для синхронізації, але небезпечним для дії, яка мала сенс лише до певного моменту. Job повинен перевіряти business deadline під час кожного attempt, а не лише в момент першого enqueue.

Backoff, maximum attempts і dead-letter handling є частиною time semantics. Без них система або надто рано здається, або виконує застарілу операцію.

Час потрібно спостерігати окремими метриками

Queue depth не показує, наскільки робота запізнилася. Вимірюйте schedule lag, queue wait, execution duration і age найстарішого pending item. Ці метрики виявляють різні причини проблеми.

Alert має спиратися на допустиму lateness бізнес-процесу. Одна хвилина може бути критичною для authorization lease і неважливою для нічного звіту.

Історія цих метрик допомагає планувати capacity до того, як затримка стане видимою користувачам.

Власник процесу має знати допустимий поріг.

Проєктуйте з урахуванням невизначеності

Надійна система сприймає час як зовнішній input з обмеженою точністю. Вона допускає skew там, де це безпечно, використовує authoritative transaction для жорстких меж і явно обробляє запізнення та дублікати.

Service-level objective має визначати допустиму затримку й спосіб виявлення missed execution. Фраза «запустити опівночі» стає технічною вимогою лише після визначення lateness, recovery та controlling timezone.