Прямой ответ
NWR — способ настроить строгость репликации тремя числами: N — сколько реплик хранит данные, W — сколько реплик должны подтвердить запись, R — сколько реплик опрашивается при чтении. Главное правило: если W + R > N, множества записи и чтения пересекаются, и чтение всегда увидит последнюю подтверждённую запись. Это и есть строгая (линеаризуемая по одному ключу) согласованность в рамках ключа.
Типовые конфигурации при N = 3:
| N | W | R | Поведение |
|---|---|---|---|
| 3 | 3 | 1 | Быстрое чтение, медленная запись, отказ одной реплики блокирует запись |
| 3 | 2 | 2 | Пересечение гарантировано, сбалансированный вариант (кворум) |
| 3 | 1 | 1 | Максимальная скорость, чтения могут вернуть устаревшие данные |
| 3 | 3 | 3 | Максимальная строгость, минимальная доступность |
Важно понимать ограничения:
- Формула не равна ACID.
W + R > Nдаёт «последнюю записанную версию» по ключу, но не транзакции между ключами и не разрешение причинных связей — для этого нужны векторные часы и, при необходимости, LWW- или CRDT-стратегии. - «Слоупай»-эффект. Кворум не спасает от чтения устаревшей версии, если часть реплик отвечает медленно: клиент получает
Rответов, а самая свежая реплика ещё не ответила. Лечитсяread_repairиspeculative retry. - Расположение реплик. Реплики размещаются по кольцу с помощью консистентного хеширования, поэтому настройка
Nвлияет и на распределение нагрузки.
Практика: настройки в Cassandra
Уровни согласованности. В Cassandra кворум задаётся уровнем: ONE, QUORUM, LOCAL_QUORUM, EACH_QUORUM, ALL. QUORUM вычисляется как (RF / 2) + 1, где RF — replication factor (то есть N).
-- таблица с RF=3 в датацентре dc1
CREATE KEYSPACE shop
WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 3};
-- строгая запись и чтение в пределах датацентра
INSERT INTO shop.orders (id, total) VALUES (1001, 250)
USING CONSISTENCY LOCAL_QUORUM;
SELECT total FROM shop.orders WHERE id = 1001
USING CONSISTENCY LOCAL_QUORUM;
Практические рекомендации:
LOCAL_QUORUMвместоQUORUMдля многодатацентровых кластеров: запись остаётся в своём DC, задержки предсказуемы, а кросс-DC трафик не влияет на p99.W + R > Nпроверяйте по факту.LOCAL_QUORUMна запись +LOCAL_QUORUMна чтение приRF=3даёт2 + 2 > 3— пересечение есть. АLOCAL_ONE+LOCAL_ONE=1 + 1 = 2, что не больше 3 — строгости нет.- Read repair и anti-entropy. Расхождения между репликами устраняются фоновым
repair; без него устаревшие версии могут жить долго, особенно при редких записях в ключ. - Мониторинг. Следите за
pending-задачами repair, числом таймаутов и долей операций с уровнем ниже кворума — это источник «фантомных» несоответствий, которые потом разбирают как инциденты.
Как выбирать уровень под сценарий. Практический подход — разделить операции на три класса. Первый: деньги, статусы заказов, инвентарь — здесь кворум на запись и кворум на чтение обязательны, а экономия на задержке недопустима. Второй: персональные настройки, содержимое профиля — допустима запись кворумом и чтение LOCAL_ONE, потому что расхождение в сотни миллисекунд не влияет на бизнес-логику. Третий: счётчики показов, телеметрия, логи — достаточно LOCAL_ONE на запись, а потеря отдельных значений приемлема; такой поток дешевле и не создаёт нагрузки на кворум.
Проверка конфигурации тестом. Полезно держать автоматическую проверку, что выбранная пара уровней действительно даёт строгость: записываем уникальное значение кворумом, затем читаем выбранным уровнем в цикле и фиксируем долю устаревших ответов. Если доля ненулевая при ожидаемой строгости — конфигурация не соответствует требованиям, и это видно до инцидента, а не во время него.
Практический сценарий: чтение вернуло старое значение при кворуме
Контекст: Cassandra, RF=3, запись LOCAL_QUORUM, чтение LOCAL_ONE. Пользователь обновил адрес, но в интерфейсе остался старый.
- Диагностика. Проверяем уровень согласованности в коде: чтение оказалось
LOCAL_ONE, то естьW + R = 2 + 1 = 3, что не большеN=3— формальной гарантии нет. - Проверка реплик. Запрос
SELECT ... USING CONSISTENCY ALL(илиnodetool getendpoints+ точечные запросы) показывает: одна реплика содержит новую версию, две — старую, запись на третью не успела из-за таймаута. - Исправление. Переводим чтение на
LOCAL_QUORUM; включаемread_repairдля горячих таблиц; добавляемspeculative retryдля снижения хвостовых задержек. - Регресс. Тест на несогласованность: пишем значение, читаем кворумом, сверяем; повторяем под нагрузкой. Такую проверку удобно держать в интеграционных тестах на Testcontainers.
- Компромисс. Для экранов, где свежесть не критична (лента, аналитика), осознанно оставляем слабые чтения и фиксируем решение в ADR.
Результат: строгие сценарии переведены на кворум, слабые чтения остались, но задокументированы. Вывод: NWR — это не «включить по умолчанию», а явный выбор по каждому сценарию; см. также модели согласованности.
FAQ
Что значит «кворум» на практике?
Это минимальное число реплик, которые должны ответить, чтобы операция считалась выполненной. Для N=3 кворум равен 2, для N=5 — 3. Кворум гарантирует пересечение с любым другим кворумом, что и обеспечивает видимость последней записи.
Можно ли сделать W=1, R=1 и всё равно получить согласованность?
Нет. 1 + 1 = 2, что не больше N=3, поэтому чтение может попасть на реплику, которая не участвовала в записи. Такая конфигурация приемлема для счётчиков показов или логов, но не для балансов и статусов заказов.
Как NWR соотносится с теоремой CAP?
NWR — это «ручка» внутри AP-архитектуры: поднимая W и R до кворума, вы жертвуете доступностью при отказах ради согласованности. Полностью CP-системы (например, на Raft) не дают такой настройки — они всегда требуют большинства. Обзор компромисса — в статье про теорему CAP.
Что делать с конфликтами одновременных записей? Кворум не разрешает конфликты, он лишь гарантирует видимость. Стратегии разрешения — last-write-wins по таймстампу, приложение-уровневое слияние или CRDT для структур, которые должны сходиться автоматически.
Источники: Cassandra docs (consistency), Wikipedia (quorum), Amazon Dynamo paper (SOSP 2007) — ссылки в шапке.