
Кворум и согласованные данные
Кворум — это правило, сколько узлов должно согласиться, чтобы операция считалась успешной.
Когда база данных распределена между несколькими узлами (репликами), становится сложно гарантировать, что все видят одно и то же состояние.
Одна из ключевых идей, которая помогает в этом — кворум (quorum).
Представим, что у нас есть 3 сервера базы:
- Node A
- Node B
- Node C
Пользователь А обновляет баланс счёта на Node A.
Через 1 мс пользователь B делает запрос, но попадает на Node C, где данные ещё не успели обновиться.
В итоге:
- A видит баланс 50 ₽
- B видит старое значение 100 ₽
Система не линеаризуема, потому что пользователи видят разные версии данных.
Как кворум решает эту проблему
Пример:
У нас 3 реплики.
- Для записи мы требуем W = 2 подтверждения.
- Для чтения — R = 2.
Общее правило:
Если W + R > N (где N — число реплик), система гарантирует линеаризуемость.
Пример шаг за шагом
Пользователь А делает запись:
UPDATE balance = 50
Запись отправляется на все 3 реплики, но считается успешной, когда 2 из 3 подтвердят (например, A и B). Node C может обновиться чуть позже.
Пользователь B делает чтение:
SELECT balance
Клиент читает данные с 2 из 3 узлов (например, B и C).
Даже если C ещё не обновился, B вернёт новое значение — и система выберет самую свежую версию.
Результат:
Оба пользователя видят баланс 50 ₽.
Когда W + R > N, хотя бы одна реплика будет общей между чтением и записью.
Это значит, что чтение обязательно увидит последнюю записанную версию — и данные останутся согласованными.
Кворум делает так, что чтение и запись всегда пересекаются хотя бы на одной “честной” реплике.
Это гарантирует, что даже при задержках синхронизации, пользователи не увидят старые данные.
Нарушение линеаризуемости
Пример:
У нас есть три реплики (три узла базы данных):
- Реплика 1
- Реплика 2
- Реплика 3
Начальное значение переменной x = 0.
Клиент отправляет запрос set x = 1 на все 3 узла.
- Реплика 1 обновилась быстро
- Реплика 2 и 3 — пока ещё нет (задержка в сети)
Запись считается успешной, когда 2 из 3 реплик (то есть кворум W = 2) подтвердили.
Но на самом деле обновление ещё не дошло до всех.
Клиент A запрашивает значение x.
Он читает данные из двух узлов (R = 2), например:
- Реплика 1 (уже обновилась) → x = 1
- Реплика 2 (ещё нет) → x = 0
Чтобы получить итог, клиент выбирает самую свежую версию — видит x = 1.
Теперь клиент B отправляет свой запрос чуть позже во времени, после клиента A.
Он тоже читает из двух узлов (R = 2), но попадает, например, на:
- Реплика 2 → x = 0
- Реплика 3 → x = 0
И получает x = 0.
Что пошло не так
Хотя клиент B обращается позже, он видит старые данные,
а клиент A, который читал раньше, — уже новые.
Это нарушает линеаризуемость,
потому что операции происходят не в логическом порядке времени.
Проблема не в том, что кворум “неправильный”.
Проблема в сетевых задержках и разных путях доставки данных.
Даже если формула кворума W + R > N выполняется,
реальные запросы могут попасть на разные комбинации узлов,
и часть из них ещё не получит свежие данные.
Представь трёх продавцов, которые делят склад:
- Ты обновляешь цену товара до 100₽ и говоришь двум продавцам из трёх.
- Первый продавец уже обновил, второй ещё нет, третий не в курсе.
- Покупатель А спрашивает цену у продавца 1 и 2 → видит 100₽.
- Покупатель Б чуть позже спрашивает у продавца 2 и 3 → видит 80₽.
Хотя Б пришёл позже, он видит старую цену — потому что спросил не у тех.
Вот это и есть нелинеаризуемость при строгом кворуме.
Как это исправить
Сделать чтение синхронным и объединяющим
— Клиент должен не просто брать данные с двух узлов,
а объединять версии и разрешать конфликты чтения (read repair).
Добавить “чтение через лидера” (read-from-leader)
— Все записи и чтения идут через один главный узел,
который гарантирует порядок операций.
Использовать консенсус (Raft / Paxos)
— Тогда все узлы применяют операции строго в одном и том же порядке.
Это дороже по времени, но зато гарантирует линеаризуемость.
Даже при правильном кворуме данные могут выглядеть “перепутанными во времени”,
потому что сеть не синхронна.
Чтобы это исправить - нужно либо читать только с лидера, либо синхронизировать версии при чтении.