Усі хочуть оновлень у реальному часі, поки не усвідомлять, що «швидко» і «правильно» — це не одне й те саме. У розподіленій системі події можуть рухатися зі швидкістю світла і все одно надходити у неправильному порядку. WebSockets розривають з'єднання та перепідключаються. Брокери повідомлень повторно надсилають пакети. Фонові воркери змагаються з тайм-аутами та скасуваннями. Результат? Клієнт може побачити подію 42, потім подію 40, а потім знімок стану (snapshot), який стверджує, що система вже перебуває на події 45. Якщо ви будуєте тривалі робочі процеси (workflows) агентів, цей хаос — не виняток. Це норма. Спочатку налагодіть порядок подій, а вже потім турбуйтеся про скорочення часу доставки на мілісекунди.

Хаотична реальність «реального часу»

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

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

Порядкові номери — це справжній годинник

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

ID подій все ще корисні, але вони вирішують іншу проблему. ID події ідентифікує конкретне корисне навантаження (payload), щоб ви могли дедуплікувати його, якщо брокер надішле те саме повідомлення двічі. Порядковий номер, з іншого боку, вказує, де це навантаження знаходиться в причинно-наслідковому ланцюгу. Він виявляє пропуски. Він виявляє порядок. Мітка часу (timestamp) не робить ні того, ні іншого. Годинники збиваються, NTP робить кроки назад, а віртуальні машини ставляться на паузу. Використовуйте мітки часу лише для відображення, наприклад, «Почато 3 хвилини тому», і ніколи не використовуйте їх як ключ сортування для бізнес-логіки.

Як клієнту слід обробляти потік

Щойно продюсер гарантує монотонну послідовність, споживач (consumer) отримує прості та жорсткі правила. Якщо вхідний порядковий номер менший або дорівнює останньому застосованому номеру, відкиньте його. Це або дублікат, або застаріле повідомлення, що запізнилося. Якщо номер на одиницю більший за останній застосований, застосуйте його негайно. Це ідеальний сценарій. Якщо послідовність стрибає вперед — наприклад, ви очікували 12, але отримали 15 — щось пропущено. Забуферуйте нову подію та запитайте сервер про відтворення (replay), починаючи з наступного очікуваного номера. Не гадайте. Не перестрибуйте вперед у надії, що пропуск не матиме значення.

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