Модуль: Распределённые системы · Уровень: Senior+
TL;DR#
- Consistency model — это контракт между хранилищем и приложением: какие чтения допустимы при заданной истории операций. Сильнее модель ⇒ проще рассуждать ⇒ дороже по latency/availability.
- Linearizability (strong/atomic consistency) — recency-гарантия над одним объектом: как только запись завершилась, все последующие чтения видят её (или более новое значение). Есть единый total order, согласованный с реальным временем.
- Serializability — isolation-гарантия над транзакциями (мульти-объектными): результат конкурентного выполнения транзакций эквивалентен какому-то последовательному порядку. Не обязана уважать реальное время.
- Strict serializability = serializability + linearizability — эквивалент последовательному порядку, который к тому же уважает реальное время (Spanner: external consistency).
- Eventual consistency — слабейшая полезная модель: при прекращении записей реплики сходятся. Ничего не говорит про порядок «во время».
- Causal consistency — сохраняет порядок причинно-связанных операций (happens-before); самая сильная модель, достижимая при сохранении доступности во время партиции.
- Client-centric / session guarantees: read-your-writes, monotonic reads, monotonic writes, writes-follow-reads — гарантии в рамках сессии одного клиента, удобный компромисс.
Теория#
Зачем нужны модели согласованности#
В одной машине память «просто работает»: записал — прочитал. В распределённой системе с репликами и кэшами «последнее значение» становится неопределённым: чтение с другой реплики может вернуть старое. Модель согласованности формально фиксирует, какие исходы чтений система обещает не допускать. Это нужно, чтобы разработчик мог рассуждать о корректности, не зная деталей репликации.
Главный трейдофф: сильнее модель → больше синхронной координации → выше latency и ниже availability (см. CAP/PACELC).
Спектр моделей (от сильной к слабой)#
strict serializability (Spanner external consistency)
│ (txns + real-time)
linearizability (single-object, real-time recency) ← "strong consistency"
│
sequential consistency (общий порядок, но не обязан уважать real-time)
│
causal consistency (happens-before сохраняется) ← макс. при сохранении availability
│
session guarantees (read-your-writes, monotonic reads/writes, writes-follow-reads)
│
eventual consistency (реплики сходятся "когда-нибудь")Линейного «одного» спектра на самом деле нет (модели образуют частичный порядок), но как первое приближение порядок выше полезен.
Linearizability (strong / atomic)#
Гарантия: существует total order над всеми операциями такой, что:
- Он согласуется с real-time: если операция A завершилась до начала операции B (по абсолютному времени), то A предшествует B в порядке.
- Каждое чтение возвращает значение последней предшествующей записи в этом порядке.
Интуиция: каждая операция выглядит так, будто произошла мгновенно в некоторой точке между её вызовом и завершением. Система ведёт себя как один регистр без реплик.
Реальное время →
Клиент A: ──[ write x=1 ]──┐
Клиент B: ──[ read x ]── должен вернуть 1
↑ если read начался ПОСЛЕ завершения write,
linearizability обязывает вернуть 1 (или новее)- Это per-object свойство (один регистр/ключ). Не про транзакции над несколькими ключами.
- Это «C» из CAP. Требует синхронной координации (кворум/лидер) ⇒ дорого.
- Композируема: если каждый объект линеаризуем, система из этих объектов тоже линеаризуема (важное отличие от serializability).
Serializability#
Гарантия: результат конкурентного выполнения набора транзакций (каждая — над произвольным множеством объектов) эквивалентен некоторому последовательному (serial) их выполнению.
- Это isolation-свойство (ACID-I), про транзакции, а не про recency.
- Сериализуемость не уважает real-time: допустим, T1 закоммитилась до начала T2, но эквивалентный serial-порядок может поставить T2 перед T1. Чтение в T2 законно может «не увидеть» коммит T1.
- Не композируема сама по себе так, как linearizability.
Linearizability vs Serializability — ключевое различие#
Это любимый вопрос на senior+.
| Linearizability | Serializability | |
|---|---|---|
| Про что | Recency (свежесть) | Isolation (изоляция транзакций) |
| Область | Один объект, одна операция | Много объектов, транзакции |
| Real-time | Уважает (total order согласован с реальным временем) | Не обязана уважать |
| Откуда термин | Распределённые системы (Herlihy & Wing) | Теория БД (ACID-I) |
| Композируемость | Да (локальное свойство) | Нет |
Strict serializability = serializability ∧ linearizability: эквивалентный serial-порядок к тому же уважает real-time. Это то, что даёт Spanner (под именем external consistency) благодаря TrueTime. Самая сильная практическая модель для транзакционных систем.
single-object multi-object/txn
recency only linearizability —
isolation only — serializability
recency + iso — strict serializabilityКратко: linearizability — про «когда» (свежесть), serializability — про «как переплелись транзакции» (изоляция). Strict serializability — оба сразу.
Sequential consistency#
Существует total order, согласованный с program order каждого клиента (операции одного клиента в порядке их выдачи), но не обязан согласовываться с real-time между разными клиентами. Слабее linearizability: чтение может вернуть устаревшее значение, если только не нарушает порядок внутри клиентов. Модель памяти многих языков/процессоров близка к этому.
Causal consistency#
Сохраняет порядок только для причинно-связанных (happens-before, Lamport) операций; конкурентные (causally independent) операции разные узлы могут видеть в разном порядке.
Happens-before (→):
- операции одного процесса упорядочены;
- send → receive того же сообщения;
- транзитивность.
A: write post = "hi" ──┐ (causal: коммент видит пост)
B: (видит post) └─→ write comment = "re: hi"
Любой узел, увидевший comment, ОБЯЗАН уже видеть post.
Но два независимых поста разные узлы могут увидеть в разном порядке.Важность: causal consistency — самая сильная модель, совместимая с высокой доступностью во время партиции (теорема: convergent + causal — потолок для AP-систем). Реализуется через vector clocks / version vectors. Предотвращает аномалии типа «вижу ответ, но не вижу исходное сообщение».
Session / client-centric guarantees#
Гарантии в рамках сессии одного клиента (не глобальные). Терпимы к слабой репликации, но устраняют самые раздражающие аномалии для конкретного пользователя.
- Read-your-writes (read-my-writes): после того как клиент записал значение, его последующие чтения видят это значение (или новее). Без этого пользователь меняет аватар и видит старый.
- Monotonic reads: если клиент прочитал значение, последующие чтения не вернут более старое. Без этого время «прыгает назад» при переключении реплик.
- Monotonic writes: записи одного клиента применяются на репликах в порядке их выдачи. Без этого
set name; set emailможет примениться в обратном порядке. - Writes-follow-reads (session causality): если клиент прочитал значение, а затем сделал запись, эта запись логически следует за прочитанным значением на всех репликах. Обеспечивает причинность на уровне сессии (комментарий после прочтения поста).
Session consistency обычно = read-your-writes + monotonic reads + monotonic writes в рамках сессии. Часто реализуется через sticky sessions (клиент закреплён за репликой) или передачу версии/токена (например, Cassandra LWT, MongoDB causal consistency tokens, Cosmos DB session tokens).
Эти гарантии слабее causal (только своя сессия), но сильнее голого eventual и закрывают 90% UX-проблем.
Eventual consistency#
Слабейшая практически используемая модель: если записи прекратятся, все реплики в конце концов сойдутся к одному значению (liveness-гарантия). Во время записей — почти никаких гарантий о порядке/свежести.
- Не определяет, какое значение победит при конфликте — это решает разрешение конфликтов (LWT по timestamp, vector clocks + siblings в Riak, CRDT).
- «Eventually» без верхней границы по времени; на практике — десятки мс — секунды.
- Дёшево и максимально доступно ⇒ дефолт для Dynamo/Cassandra/DNS.
- Strong eventual consistency (CRDT): реплики, получившие один набор обновлений (в любом порядке), гарантированно в одном состоянии — без координации, через коммутативные/идемпотентные merge.
Где это всплывает в Go#
В Go-сервисах модель согласованности обычно «спускается» из выбранного хранилища, но клиентский код обязан её учитывать:
// Анти-паттерн: read-after-write на eventually consistent реплике
_, err := db.ExecContext(ctx, "INSERT INTO orders ...") // пишем в primary
if err != nil { return err }
// читаем со read-replica — можем НЕ увидеть только что вставленное
row := replica.QueryRowContext(ctx, "SELECT ... FROM orders WHERE id=$1", id)Решения: читать с primary для read-your-writes критичных путей; передавать версию/LSN и ждать её на реплике; sticky routing; или строить инвариант так, чтобы stale-read был безопасен (идемпотентность, повтор).
Подводные камни / gotchas#
- Linearizability ≠ serializability. Первое — recency над одним объектом, второе — isolation над транзакциями. Их регулярно путают; strict serializability — это их объединение.
- Serializability не уважает real-time. Транзакция может законно «не увидеть» только что закоммиченную другую транзакцию, если эквивалентный serial-порядок ставит её раньше. Для real-time нужна strict serializability.
- Eventual consistency не разрешает конфликты сама. «Сойдутся» — да, но к чему зависит от стратегии (LWW теряет записи; vector clocks дают siblings; CRDT мержит без потерь).
- Read-your-writes легко сломать read-репликами. Запись в primary, чтение с асинхронной реплики — классический баг «обновил профиль, вижу старый».
- Monotonic reads ломается при балансировке между репликами без sticky/версионности: пользователь видит то новые, то старые данные.
- Causal consistency требует трекинга зависимостей (vector clocks), что добавляет метаданные и сложность; «дешёвая» causal-репликация — нетривиальна.
- Сильная модель на запись ≠ сильная на чтение. Многие системы тюнят отдельно (Cassandra: write CL и read CL независимы; ZooKeeper: линеаризуемые записи, но чтения могут быть stale без sync()).
- Линеаризуемость дорога и не всегда нужна. Навешивать strong consistency на всё — это лишняя latency; senior выбирает минимально достаточную модель на каждый путь.
Вопросы на собеседовании#
В: В чём разница между linearizability и serializability? О: Linearizability — recency-гарантия над одним объектом: единый порядок операций, согласованный с реальным временем, чтение видит последнюю завершившуюся запись. Serializability — isolation-гарантия над транзакциями (много объектов): результат эквивалентен какому-то последовательному порядку, но real-time не обязан соблюдаться. Первое из распределённых систем, второе — из теории БД (ACID-I).
В: Что такое strict serializability и кто её даёт? О: Это serializability + linearizability: эквивалентный serial-порядок транзакций к тому же уважает реальное время (если T1 закоммитилась до начала T2, то T1 в порядке раньше). Google Spanner предоставляет её под названием external consistency, используя TrueTime для синхронизации часов.
В: Может ли сериализуемая система вернуть устаревшие данные? О: Да. Сериализуемость не обязана уважать real-time: транзакция T2, начавшаяся после коммита T1, может законно оказаться в эквивалентном serial-порядке перед T1 и не увидеть её изменений. Чтобы это исключить, нужна strict serializability.
В: Что гарантирует eventual consistency и чего не гарантирует? О: Гарантирует, что при прекращении записей реплики сойдутся к одному состоянию. Не гарантирует порядок или свежесть во время записей и сама по себе не определяет, какое значение победит в конфликте — это задача стратегии разрешения (LWW, vector clocks, CRDT).
В: Что такое read-your-writes и как её обеспечить при наличии read-реплик? О: Гарантия, что клиент после своей записи видит её (или новее) в последующих чтениях. При асинхронных репликах обеспечивается чтением с primary для своих недавних записей, sticky-сессией к одной реплике, или передачей версии/LSN записи и ожиданием, пока реплика догонит её.
В: Чем monotonic reads отличается от read-your-writes? О: Monotonic reads: последующие чтения клиента не возвращают более старое значение, чем уже прочитанное (время не идёт назад). Read-your-writes: чтения видят собственные записи клиента. Первое про монотонность чтений, второе про видимость своих записей — это разные session guarantees.
В: Почему causal consistency особенная среди слабых моделей? О: Это самая сильная модель согласованности, совместимая с полной доступностью во время сетевой партиции (доказано, что «причинность + сходимость» — потолок для AP-систем). Она сохраняет порядок причинно-связанных операций (happens-before), устраняя аномалии вроде «вижу ответ без исходного сообщения», и при этом не требует глобальной синхронной координации.
В: Композируемы ли linearizability и serializability? О: Linearizability — да: если каждый объект линеаризуем, вся система из этих объектов линеаризуема (локальное свойство). Serializability сама по себе не композируема таким образом — её надо обеспечивать на уровне всего набора транзакций.
В: Что такое session consistency и как её реализуют на практике? О: Набор гарантий в рамках сессии одного клиента — обычно read-your-writes + monotonic reads + monotonic writes. Реализуется через sticky-сессии (закрепление клиента за репликой) либо через session/version-токены, которые клиент носит с собой и которые заставляют реплику обслужить запрос не старее указанной версии (Cosmos DB session tokens, MongoDB causal consistency).
На что копают на senior+#
- Чёткое разведение linearizability (recency, single-object) vs serializability (isolation, multi-object) и понимание, что strict serializability — их объединение (Spanner / external consistency).
- Понимание, что serializability не уважает real-time, и умение привести конкретную аномалию, которую это допускает.
- Знание спектра и того, что это частичный порядок, а не одна линия; умение разместить sequential, causal, session guarantees.
- Понимание causal как потолка для AP и механики happens-before / vector clocks.
- Знание session guarantees поимённо (read-your-writes, monotonic reads/writes, writes-follow-reads) с конкретными UX-аномалиями каждой и способами обеспечить (sticky, version tokens).
- Понимание стоимости: сильная согласованность = синхронная координация = latency/availability-цена (связь с CAP/PACELC), и умение выбрать минимально достаточную модель на конкретный путь.
- Практика: read-after-write на read-репликах, разрешение конфликтов при eventual (LWW теряет данные, CRDT/vector clocks — нет), независимый тюнинг read/write consistency.