GH GambleHub

Реплікація та eventual consistency

Реплікація та eventual consistency

1) Навіщо eventual consistency

Коли система розподілена по зонах/регіонах, синхронний запис скрізь дає високу латентність і низьку доступність при мережевих збоях. Eventual consistency (EC) допускає тимчасову розсинхронізацію реплік заради:
  • низької затримки запису (локальний прийом),
  • кращої доступності при поділах мережі,
  • горизонтального масштабування.

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


2) Моделі узгодженості - що обіцяємо клієнту

Strong: читання відразу бачить останній запис.
Bounded stale / read-not-older-than (RNOT): читання не старі позначки (LSN/версія/час).
Causal: зберігаються «причинно-наслідкові» відносини (A до B).
Read-Your-Writes: клієнт бачить свої недавні записи.
Monotonic Reads: кожне наступне читання не «відкочується» назад.
Session: набір гарантій в рамках однієї сесії.
Eventual: при відсутності нових записів всі репліки сходяться.

Практика: комбінуйте Session + RNOT на критичних шляхах і Eventual на вітринах/кешах.


3) Реплікація: механіки та anti-entropy

Синхронна (кворум/RAFT): запис вважається успішним після підтвердження N вузлами; мінімальний RPO, вище p99.
Асинхронна: лідер комітить локально, роздає журнал пізніше; низька латентність, RPO> 0.
Фізична (WAL/binlog): швидка, гомогенна.
Логічна/CDC: потік змін на рівні рядків/подій, гнучка маршрутизація, фільтри.
Anti-entropy: періодичні звірки і лагодження (Merkle-дерева, порівняння хешів, фоновий re-sync).


4) Ідентифікатори версії та замовлення причинності

Монотонні версії: increment/LSN/epoch; просто, але не кодують паралелізм.
Lamport timestamp: частковий порядок за логічним годинником.
Vector clock: фіксує паралельні гілки і дозволяє детектувати конфліктні апдейти (concurrent).
Hybrid/TrueTime/Clock-SI: логіка «не раніше T» для глобального порядку.

Рекомендація: для CRDT/конфліктних апдейтів - vector clock; для «не старіє» - LSN/GTID.


5) Конфлікти: виявлення та дозвіл

Типові ситуації: запис з двох регіонів в один і той же об'єкт.

Стратегії:

1. Last-Write-Wins (LWW) за годинниковим/логічним штампом - просто, але може «втратити» апдейти.

2. Merge-функції з доменної логіки:
  • поля-лічильники складаються (G-Counter/PN-Counter),
  • множини об'єднуються з «add-wins/remove-wins»,
  • суми/баланси - тільки через транзакційні журнали, не через просте LWW.
  • 3. CRDT (конвергентні типи): G-Counter, OR-Set, LWW-Register, RGA для списків.
  • 4. Операційні трансформації (рідко для БД, частіше для редакторів).
  • 5. Manual resolution: конфлікт в «inbox», користувач вибирає вірну версію.

Правило: інваріанти домену диктують стратегію. Для грошей/залишків - уникайте LWW; використовуйте транзакції/події з компенсацією.


6) Гарантії записів та ідемпотентність

Ідемпотентні ключі на командах (payment, withdraw, create) → повтор безпечний.
Дедуплікація на «вході» (inbox) і «виході» (outbox) по ключу ідемпотентності/серійному номеру.
Exactly-once недосяжно без сильних передумов; практикуйте at-least-once + ідемпотентність.
Outbox/Inbox-патерн: запис в БД і публікація події атомарни (локальна транзакція), одержувач обробляє по idempotency-key.


7) Читання «не старіє X» (RNOT)

Техніки:
  • LSN/GTID-гейт: клієнт передає мінімальну версію (з відповіді запису), роутер/проксі направляє на репліку, що наздогнала LSN ≥ X, інакше - на лідер.
  • Time-bound: «не старіє 2 сек» - простий SLA без версій.
  • Session pinning: після запису N секунд читаємо тільки лідера (Read-Your-Writes).

8) Потоки змін і узгодження кешів

CDC → шина подій (Kafka/Pulsar) → споживачі (кеші, індекси, вітрини).
Інвалідація кешів: топіки'invalidate:{ns}:{id}`; idempotent обробка.
Rebuild/Backfill: при розсинхроні пересберіть проекції з журналу подій.


9) Саги і компенсації (міжсервісні транзакції)

У EC-світі довгоживучі операції розбиваються на кроки з компенсуючими діями:
  • Оркестрація: координатор викликає кроки та їх компенсації.
  • Хореографія: кроки реагують на події і самі публікують наступні.

Інваріанти (приклад): «баланс ≥ 0» - перевірка на межах кроку + компенсація при відхиленні.


10) Мульти-регіон і мережеві поділи

Local-write, async-replicate: запис в локальному регіоні + доставка в інші (EC).
Geo-fencing: дані «приклеєні» до регіону (низька латентність, менше конфліктів).
Кворумні БД (Raft) для CP-даних; кеші/вітрини - AP/EC.
Split-brain план: при втраті зв'язку регіони продовжують працювати в рамках доменних лімітів (write fencing, квоти), потім - reconcile.


11) Спостережуваність і SLO

Метрики:
  • Replica lag: час/LSN-дистанція/offset (p50/p95/p99).
  • Staleness: частка відповідей старше порога (наприклад,> 2s або LSN
  • Conflict rate: частота конфліктів і успішних merge.
  • Convergence time: час сходження реплік після піку.
  • Reconcile backlog: обсяг/час відстаючих партій.
  • RPO/RTO за категоріями даних (CP/AP).
Алерти:
  • Лаг> цільового, зростання конфліктів, «довгі» вікна незхідності.

12) Проектування схеми даних під EC

Явна версія/вектор в кожному записі (колонки'version','vc').
Append-only журнали для критичних інваріантів (баланси, нарахування).
Ідентифікатори подій (snowflake/ULID) для порядку і дедупа.
Поля з комутативною природою (лічильники, множини) → кандидати на CRDT.
Дизайн API: PUT с if-match/etag, PATCH с precondition.


13) Патерни зберігання і читання

Read model / CQRS: запис в «джерело», читання з проекцій (можуть відставати → відображайте «оновлюється»...).
Stale-OK маршрути (каталог/стрічка) vs Strict (гаманець/ліміти).
Sticky/Bounded-stale прапори в запиті (заголовок'x-read-consistency').


14) Чек-лист впровадження (0-45 днів)

0-10 днів

Категоризувати дані: CP-критичні (гроші, замовлення) vs ЄС/стале-OK (каталоги, пошукові індекси).
Визначити SLO стейла (наприклад, «не старіє 2s»), цільові лаги.
Включити версіонування об'єктів і idempotency-keys в API.

11-25 днів

Впровадити CDC і outbox/inbox, маршрути інвалідації кеша.
Додати RNOT (LSN-гейт) і session pinning на запис-критичних шляхах.
Реалізувати мінімум одну merge-стратегію (LWW/CRDT/доменна) і журнал конфліктів.

26-45 днів

Автоматизувати anti-entropy (звірки/лагодження) і звіти по стейлу.
Провести game-day: поділ мережі, сплеск конфліктів, відновлення.
Візуалізувати на дашбордах: lag, staleness, conflict rate, convergence.


15) Анти-патерни

Сліпий LWW для критичних інваріантів (втрата грошей/балів).
Відсутність idempotency → дублі операцій при ретраях.
«Сильна» модель на всьому → надмірні хвости p99 і крихкість при збоях.
Немає RNOT/Session гарантій → UX «блимає», користувачі «не бачать» свої зміни.
Прихована розсинхронізація кеша і джерела (немає CDC/інвалідації).
Відсутність інструменту reconcile/anti-entropy - дані «на століття» розходяться.


16) Метрики зрілості

Replica lag p95 ≤ цільового (наприклад, ≤ 500 ms всередині регіону, ≤ 2 s міжрегіони).
Staleness SLO виконується ≥ 99% запитів на «суворих» маршрутах.
Conflict resolution success ≥ 99. 9%, середній час дозволу ≤ 1 хв.
Convergence time після піків - хвилини, не годинник.
100% «грошових» операцій покриті idempotency-ключами і outbox/inbox.


17) Рецепти (сніпети)

If-Match/ETag (HTTP)


PUT /profile/42
If-Match: "v17"
Body: { "email": "new@example.com" }

Якщо версія змінилася -'412 Precondition Failed'→ клієнт вирішує конфлікт.

Запит «не старіший LSN» (псевдо)


x-min-lsn: 16/B373F8D8

Роутер вибирає репліку з'replay _ lsn ≥ x-min-lsn', інакше - лідер.

CRDT G-Counter (ідея)

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


18) Висновок

Eventual consistency - не компроміс якості, а усвідомлений контракт: десь ми платимо свіжістю заради швидкості і доступності, але захищаємо критичні інваріанти доменними стратегіями та інструментами. Введіть версії, idempotency, RNOT/Session гарантії, CDC і anti-entropy, вимірюйте lag/staleness/conflicts - і ваша розподілена система буде швидкою, стійкою і передбачувано збігається навіть під збоями і піковими навантаженнями.

Contact

Зв’яжіться з нами

Звертайтеся з будь-яких питань або за підтримкою.Ми завжди готові допомогти!

Розпочати інтеграцію

Email — обов’язковий. Telegram або WhatsApp — за бажанням.

Ваше ім’я необов’язково
Email необов’язково
Тема необов’язково
Повідомлення необов’язково
Telegram необов’язково
@
Якщо ви вкажете Telegram — ми відповімо й там, додатково до Email.
WhatsApp необов’язково
Формат: +код країни та номер (наприклад, +380XXXXXXXXX).

Натискаючи кнопку, ви погоджуєтесь на обробку даних.