GH GambleHub

Eventual Consistency на практиці

Eventual consistency (EC) - модель, в якій копії даних можуть тимчасово розходитися, але з часом сходяться без глобальної координації. Це ключ до високої доступності (AP по CAP) і низької латентності (PACELC), якщо правильно визначити інваріанти, правила мерджа і клієнтські гарантії.

1) Коли вибирати EC (і коли - ні)

Підходить:
  • Фіди, профілі, лайки/лічильники, каталоги/пошук, кешовані уявлення.
  • Глобальні системи з локальними записами і м'якими інваріантами.
  • Проекції (CQRS), де джерело істини - суворе ядро, а читання - асинхронні.
Не підходить:
  • Жорсткі інваріанти: гроші, єдиність, ліміти, інвентар «не піти в мінус». Там - СР/сильніше EC, саги/ТСС.

2) Дизайн даних під EC: конфлікти та їх вирішення

Принцип: кожен запис несе метадані версій і детерміновану функцію злиття.

Мітки часу/версіонування: `version`, `ts`, `actor`.
Векторний годинник: фіксують причинність, дозволяють зрозуміти «конфліктуючі паралелі».

Правила мерджа:
  • LWW (Last-Write-Wins): просто і швидко, але може втратити «сенс».
  • CRDT: комутативні/ідемпотентні структури, що гарантують сходження.
  • Доменний merge: бізнес-функція (наприклад, об'єднати списки без дублів, підсумовувати лічильники, «найновіший email + об'єднання тегів»).
Вибір CRDT:
  • Лічильники → G-Counter/PN-Counter.
  • Безлічі → OR-Set (видалення без «залипання»).
  • Регістри → LWW-Register (з обережністю до «втрат»).
  • Карти/документи → Map of CRDTs.
  • Спільне редагування → текстові CRDT/OT.

3) Реплікація та анти-ентропія

Gossip/anti-entropy: періодичний обмін станами/хешами між вузлами.
Hinted handoff: тимчасове «депонування» запису для недоступного вузла.
Read repair: при читанні виявили неузгодженість - підтягнули свіжі версії.
Пакети змін (deltas): ганяємо дельти, а не повні знімки.
Кворуми R/W: налаштовуємо'R','W','N'під компроміс швидкості і свіжості (наприклад,'R + W> N'ближче до strong на «останньому записі»).

4) Клієнтські гарантії поверх EC

Read-Your-Writes (RYW): автор після свого запису бачить її (sticky-session/маркування версії).
Monotonic Reads: не «відкочуємо» клієнта на старіше значення (зберігаємо watermark останньої версії).
Causal Consistency: зберігаємо причинність в межах сесії/потоку дій (векторні мітки в заголовках/токенах).
Bounded Staleness: гарантія «не старше Δ t/N версій» для UX-критичних екранів.

5) UX-патерни для EC

Оптимістичні апдейти: моментально відображаємо дію, позначаючи «синхронізацію».
Маркування свіжості: бейдж «оновлено X сек назад», кнопка «Оновити».
Конфлікт-UI: для рідкісних колізій - «показати обидві версії і вибрати/об'єднати».
Скелетон/placeholder + soft refresh: не блокувати UI очікуванням глобальних кворумів.

6) Архітектурні шаблони

6. 1 CQRS + проекції

Write-ядро (CP): Суворі інваріанти.
Read-площина (EC): асинхронні проекції, індекси, кеші; лаг припустимо.

6. 2 Мульти-регіон AP

Записи локально швидко, реплікація асинхронно.
Geo-partitioning: дані «живуть» ближче до користувача; крос-регіон - агрегати.
CRDT/merge-функції знімають біль конфліктів.

6. 3 Кворумне налаштування

yaml consistency:
replicas: 3 # N write_quorum: 2 # W read_quorum: 2 # R => R + W> N, closer to freshness on "last record"
read_repair: true hinted_handoff: true

7) Політики версіювання і merge (приклад)

yaml entity: "profile"
versioning:
clock: "vector"    # или "hybrid_time"
fields:
name:   { merge: "lww" }
emails:  { merge: "set_union" }   # OR-Set tags:   { merge: "or_set" }
likes:   { merge: "pn_counter" }
conflict_ui:
enabled: true show_diff_for: ["name"]
auto_merge_for: ["emails","tags","likes"]

8) Спостережуваність EC: Що міряти

Staleness Age (p50/p95/p99): 'now − data_version_ts' або «число версій відставання».
Replication Lag: затримка доставки між регіонами/вузлами.
Conflict Rate: частка паралельних апдейтів, розподіл за типами.
Read-Repair Rate/Latency: як часто і як швидко «лікуємо» при читанні.
Convergence Time: час до сходження після сплеску записів/відмови вузла.
Семантичні SLO: «95% профілів не старше 2с», «99% фіду сходиться <10с».

9) Runbook'і і інциденти

Сценарії:

1. Зростання lag міжрегіонально: знизити'write fan-out', включити агресивний read-repair, троттлити важкі письменники.

2. Сплеск конфліктів: тимчасово включити більш «суворе» правило (наприклад, causal/RYW), обмежити конкурентні апдейти на гарячих ключах.

3. Відставання проекцій: приоритизувати черги реплікації, тимчасово урізати частоту не-критичних апдейтів.

4. Дані «залипли» у частини вузлів: форс-анти-ентропія, ребаланс партій, аудит hinted handoff.

5. Ручний розбір: вивантаження конфліктних ключів, інструмент «merge-preview», батчевий фікс.

10) Тестування EC

Jepsen-подібні тести: розділення мережі, clock-skew, повторні записи.
Property-based: інваріанти merge-функцій (комутативність, ідемпотентність, асоціативність).
Fuzz-конфлікти: паралельні апдейти на один ключ з варіативним порядком доставки.
Навантажувальні «пилки»: чергування бурстів/затиший для оцінки convergence time.
UX-симуляції: видимість RYW/monotonic у типових сценаріях.

11) Мульти-тенант і плани

Теги'tenant _ id/plan/region'у подіях/записах.
Fairness: ліміти на реплікацію/repair per tenant, щоб «галасливий» клієнт не збільшував загальний staleness.
Residency: дані та їх репліки в межах юрисдикції; крос-регіональні представлення тільки агрегати.

12) Типові помилки

LWW «для всього». Втрачає смислові паралельні зміни; використовуйте CRDT/доменні merge.
Немає клієнтських гарантій. Користувач «не бачить» власний запис → втрата довіри.
Відсутність спостережуваності застарівання. Немає метрик staleness/lag → «прихована деградація».
Dual-write в різні системи без merge. Фантоми і розбіжності нескінченно.
Глобальний порядок будь-якою ціною. Зайві кворуми вбивають p95, а бізнесу досить локального порядку.

13) Швидкі рецепти

Фід/стрічка: EC + causal/RYW для автора, CRDT для реакцій, staleness p95 ≤ 2-5с.
Профілі/налаштування: bounded staleness (≤1 -2с), RYW, доменний merge (union множин).
Глобальний каталог: geo-partition, асинхронна реплікація, read-repair за запитом, конфлікти через OR-Set.
Метрики/лічильники: PN-Counter, консолідація в фоні; відображення «приблизних» значень з позначкою.

14) Міні-еталон (словесна схема)

Write-edge: локальний запис з версією ('vector/hybrid'), журнал подій.
Replication: черги + gossip/anti-entropy, hinted handoff.
Storage: партіонування за ключем, CRDT/мердж-функції на рівні запису.
Read-plane: кеші з read-repair, RYW/monotonic токени, bounded staleness для критичних екранів.
Observability: лаги/застарівання/конфлікти, алерти на перевищення SLO стейлнесса.

15) Чек-лист перед продом

  • Чітко описані інваріанти і де допускається EC.
  • Вибрані версіонування (vector/hybrid) і детерміновані функції merge/CRDT.
  • Реалізовані клієнтські гарантії (RYW/monotonic/causal) для критичних UX.
  • Налаштовані реплікація, read-repair, hinted handoff; кворуми R/W документовані.
  • Метрики staleness/lag/convergence і алерти по порогах p95/p99.
  • Runbook'і на зростання конфліктів/лагів; безпечні інструменти ручного merge.
  • Тести на мережеві розділення, паралельні апдейти і властивість збіжності.
  • Мульти-тенантні ліміти і residency-політики враховані.
  • UX-індикатори свіжості і fallback-поведінка узгоджені з продуктом.

Висновок

Eventual consistency - не «компроміс заради компромісу», а інструмент масштабованості та доступності. Якщо ви формалізуєте інваріанти, виберете коректні merge-функції (бажано CRDT там, де це доречно), дасте клієнтські гарантії і будете вимірювати стейлнесс і час сходження, система буде швидкою, стійкою і чесною - і для користувачів, і для бізнесу.

Contact

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

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

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

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

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

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