System Design Space
Граф знанийНастройки

Обновлено: 24 июня 2026 г. в 12:52

Leaderless-репликация: кворумы, R+W>N и hinted handoff

эксперт

Dynamo-style leaderless-репликация: любой узел принимает запись в N реплик, согласованность через пересечение кворумов R+W>N, sloppy quorum и hinted handoff при отказах, read repair и anti-entropy с Merkle-деревьями для досогласования, version vectors / LWW / сиблинги для конкурентных записей, а также системы Amazon Dynamo, Cassandra, Riak.

Глава про leaderless-репликацию важна тем, что показывает третий путь между лидерским консенсусом и CRDT: лидера нет, запись принимает любая реплика, а согласованность держится не на координации, а на арифметике кворумов N/R/W.

На практике это инженерная основа высокодоступных хранилищ Dynamo-класса (Cassandra, Riak): запись проходит при отказах узлов и сетевом разделении, а расхождение реплик сходят hinted handoff, read repair и фоновой anti-entropy с Merkle-деревьями.

В интервью и архитектурных обсуждениях материал даёт язык, чтобы честно назвать цену: R+W>N даёт свежесть кворумного чтения, но не линеаризуемость, а sloppy quorum и конкурентные записи ломают наивные ожидания о «сильной» консистентности.

Практическая польза главы

Кворумная арифметика

Учит читать настройку N/R/W и понимать, почему именно R+W>N даёт пересечение и видимость свежей копии.

Устойчивость к отказам

Объясняет, как sloppy quorum и hinted handoff сохраняют доступность записи и как read repair с anti-entropy сходят расхождения.

Разрешение конфликтов

Различает обнаружение конкурентности (version vectors) и её разрешение (LWW, сиблинги, CRDT), показывая риск тихой потери данных.

Границы применения

Фокусирует выбор: leaderless ради доступности и AP, лидерский консенсус — там, где нужна линеаризуемость.

Контрастная соседняя глава

Консенсус: Paxos и Raft

Соседняя глава решает ту же задачу согласования реплик противоположным способом — через единого лидера, журнал команд и линеаризуемость. Эта глава убирает лидера вовсе.

Сравнить подходы

Соседняя глава про организует через единого лидера: все записи проходят через него, журнал команд течёт от лидера к репликам, а результат — история. Цена — лидер становится узким местом, а без связи с большинством запись невозможна.

Эта глава про обратный полюс. В (её ещё называют Dynamo-style по статье Amazon 2007 года) лидера нет вовсе: запись принимает любая реплика, а согласованность держится не на координации, а на арифметике . Клиент или координатор пишет в N реплик, читает из нескольких, и при условии R + W > N читающий набор гарантированно пересекается с пишущим — то есть видит хотя бы одну свежую копию.

Где это пересечение ломается, в дело вступают механизмы досогласования: при временных отказах, на самом чтении и в фоне. Конкурентные записи же приходится либо обнаруживать через , либо терять через . Разрешать конфликты содержательно — это уже задача соседней главы про .

нужна там, где запись должна проходить даже при отказе части узлов и при сетевом разделении — ценой того, что «строгой» согласованности здесь нет, а расхождения реплик приходится сходить отдельными механизмами. Это классический выбор и устойчивости к разделению в терминах , то есть AP-сторона спектра.

Фундамент

Теорема CAP

Хранилища с топологией без выделенного ведущего узла (leaderless) сознательно выбирают доступность и устойчивость к разделению (сторона AP в терминах теоремы CAP): запись проходит при разделении, но цена — отложенная согласованность и расхождение реплик.

Читать обзор

Модель: запись на любой узел, без лидера

В лидерской модели у каждого ключа есть «главная» реплика, и порядок записей определяет она. В модели такого узла нет: клиент (или координирующий узел, который просто принял запрос) шлёт запись параллельно во все N реплик ключа и ждёт W подтверждений. Чтение симметрично — клиент опрашивает реплики и собирает R ответов. Никаких выборов лидера, аренды и переключения на резерв.

Высокая доступность записи

Пока живы W из N реплик, запись проходит. Отказ узла, перезагрузка или сетевой разрыв не останавливают кластер и не требуют переизбрания лидера.

Нет узкого места и паузы на переключение (failover)

Запись не упирается в один узел и не ждёт тайм-аут выборов: любая реплика принимает запрос, нагрузка размазывается по кольцу.

Цена — согласованность

За доступность платят отложенной согласованностью: реплики временно расходятся, а конкурентные записи нужно либо обнаруживать, либо терять.

Ключевое наблюдение: координатор в системе — это не лидер. Он не упорядочивает записи и не хранит истину; он просто маршрутизатор, который рассылает запрос N репликам и считает ответы. Любой узел может стать координатором для любого запроса, поэтому отказ «координатора» ничего не значит — клиент повторит запрос через другой узел.

Проверка на практике

Jepsen и модели консистентности

Тесты Jepsen не раз показывали, что кворумное пересечение на бумаге не означает линеаризуемости: конкурентные записи и нестрогий кворум (sloppy quorum) нарушают наивные ожидания.

Читать обзор

Кворумы: N, W, R и условие R + W > N

Три числа задают всю настройку. N — число реплик каждого ключа (фактор репликации). W — сколько реплик должны подтвердить запись, прежде чем она считается успешной. R — сколько реплик опрашивает чтение. Главное правило записывается одной строкой и объясняет всю механику свежести данных.

Условие пересечения: R + W > N

Запись попадает минимум на W узлов, чтение опрашивает R узлов. Если R + W > N, то по принципу Дирихле читающий набор обязан пересечься с последним пишущим хотя бы в одном узле — и этот узел отдаст свежую версию. Так чтение «видит» последнюю запись, не спрашивая весь кластер.

Роль версий

Пересечение гарантирует, что среди R ответов есть свежий, но не говорит, какой именно. Поэтому каждая запись несёт версию (номер, метку времени или ), и чтение выбирает самую свежую среди полученных, а устаревшие копии чинит.

Почему R + W > N всегда пересекается (N = 3, W = 2, R = 2)

Три реплики ключа. Запись подтверждают 2 узла, чтение опрашивает 2 узла. Сколько бы их ни сдвигали, минимум один узел попадает в оба набора — он и отдаёт свежую версию.

Пересечение кворумов R и W при N = 3Реплика AW + RРеплика BWРеплика CRW = 2 (запись: A, B)R = 2 (чтение: A, C)Пересечение — реплика A: чтение видит свежую запись. R + W = 4 > N = 3
NWRR + W > N?Что получаем
3225 > 3 — даКлассический баланс: переживаем отказ одной реплики и при записи, и при чтении
3314 > 3 — даБыстрые чтения, дорогая запись: при отказе любой реплики запись недоступна
3134 > 3 — даБыстрая запись, дорогое чтение: чтение недоступно при отказе любой реплики
3112 > 3 — нетМаксимум доступности, но чтение может вернуть устаревшие данные
5336 > 5 — даПереживаем отказ двух реплик с сохранением пересечения

Обратите внимание на связь с лидерским кворумом из главы о консенсусе: там симметричный случай W = R = f + 1 на N = 2f + 1 узлах. Системы обобщают идею — они позволяют двигать R и W независимо под каждый запрос, торгуя задержку записи на задержку чтения. Но само по себе пересечение даёт лишь видимость свежей копии, а не единую историю.

Сравнение

Кворумы у лидера и без него

В лидерском консенсусе кворум защищает один журнал команд; в топологии без выделенного ведущего узла (leaderless) — лишь пересечение наборов узлов без общего порядка операций.

Читать обзор

Отказы записи: нестрогий кворум (sloppy quorum) и временное хранение записи (hinted handoff)

Что делать, если в момент записи W «домашних» реплик ключа недоступны — упали или отрезаны разделением? Жёсткий (strict) кворум вернул бы ошибку. Но Dynamo-системы предлагают компромисс ради доступности: sloppy quorum (нестрогий кворум) — записать W копий на любые живые узлы кольца, даже если они не входят в обычный набор реплик ключа.

Нестрогий кворум (sloppy quorum): записать на «соседа»

Если одна из N домашних реплик недоступна, координатор берёт временную замену из следующих живых узлов кольца. Запись принимается, W подтверждений собираются, клиент получает успех — кластер остаётся доступным на запись там, где строгий (strict) кворум бы отказал.

: досыл «домой»

Временный узел хранит «подсказку» (hint): данные, что предназначались упавшей реплике. Когда та возвращается, узел-замена досылает ей накопленные записи и удаляет подсказку. Так данные в итоге оказываются на правильных N узлах — отсюда «передача домой» (handoff).

Тонкий момент: нестрогий (sloppy quorum) ослабляет гарантию пересечения. W подтверждений могли лечь на временных узлов вне набора реплик ключа, а R-чтение опрашивает домашние узлы — и они ещё не получили досыл. То есть даже при R + W > N свежая запись может временно не читаться. Нестрогий кворум покупает доступность ценой того самого пересечения, на котором держится свежесть.

Согласование расхождений: восстановление при чтении (read repair) и антиэнтропийная синхронизация (anti-entropy)

Реплики неизбежно расходятся: пропущенные записи, (hinted handoff), конкурентные изменения. Чтобы они сходились обратно, есть два механизма — один работает на пути чтения, другой в фоне.

: чинить на чтении

Когда чтение опрашивает R реплик и видит, что одна из них отдала устаревшую версию, координатор записывает в неё свежее значение прямо в процессе ответа клиенту. Часто читаемые ключи так сходятся «бесплатно», попутно с обычной нагрузкой.

Слабость: чинит только то, что читают. Ключ, который никто не запрашивает, может оставаться рассогласованным сколь угодно долго.

: фоновая сверка

Фоновый процесс сравнивает наборы данных между репликами и подтягивает недостающее — независимо от того, читают ключи или нет. Чтобы не пересылать всё подряд, реплики сравнивают Merkle-деревья: хеш-деревья диапазонов ключей.

Совпали корневые хеши — диапазоны идентичны, передавать нечего. Разошлись — спускаемся по дереву и находим ровно те поддиапазоны, что разъехались, пересылая только их.

Три механизма закрывают разные классы отказов: (hinted handoff) — временные сбои (узел вернётся через минуты), с Merkle-деревьями — перманентную потерю (узел заменили с пустым диском), а (read repair) — повседневный дрейф на горячих ключах. Вместе они и обеспечивают .

Соседняя глава

Бесконфликтно реплицируемые типы данных (CRDT) и слияние без координации

Сиблинги — это конкурентные версии, которые хранилище с топологией без выделенного ведущего узла (leaderless) возвращает приложению. Бесконфликтно реплицируемые типы данных (CRDT) дают способ сливать их детерминированно на уровне типа данных, а не выбором одной.

Читать обзор

Разрешение конфликтов: векторы версий (version vectors), стратегия «последняя запись побеждает» (LWW) и сиблинги

Раз записи принимает любой узел, две клиента могут параллельно изменить один ключ, не зная друг о друге. Это конкурентные записи — и их сначала нужно отличить от обычного «новее/старее», а потом как-то разрешить.

: обнаружить конкурентность

Каждая реплика держит вектор счётчиков версий по ключу — по одному счётчику на узел в группе репликации. Сравнивая векторы, система понимает: одна запись причинно следует за другой (тогда новее побеждает) или они конкурентны (ни одна не видела другую). Это обнаружение конфликта, а не его разрешение.

Стратегия «последняя запись побеждает» (LWW): просто и с риском потери

разрывает конфликт по метке времени: побеждает «последняя» запись, остальные молча отбрасываются. Просто и без сиблингов, но при рассинхронизации часов теряет реальные данные — поэтому годится лишь там, где потеря конкурентной правки допустима.

Сиблинги: вернуть конфликт приложению

Dynamo и Riak сохраняют конкурентные версии как сиблинги (siblings) и отдают их все при чтении. Приложение решает само: слить корзины, показать обе правки или применить доменное правило. Ответственность за смысл переносится наверх.

Канонический пример из статьи Dynamo — корзина покупок: при слиянии сиблингов лучше «оживить» удалённый товар, чем потерять добавленный. Именно эту проблему — детерминированно сливать конкурентные версии, не теряя операций, — системно решают из соседней главы: они задают тип данных так, что слияние коммутативно и не зависит от порядка доставки.

Гарантии и заблуждения: чего R + W > N на самом деле не даёт

Самое частое заблуждение — что R + W > N даёт «сильную» консистентность. Не даёт. Пересечение гарантирует лишь, что чтение увидит хотя бы одну свежую копию; это монотонная свежесть кворумного чтения, но не .

Заблуждение: «кворум = линеаризуемость»

  • Нестрогий (sloppy quorum) не гарантирует пересечение: W копий могли лечь на временные узлы вне набора реплик.
  • Конкурентные записи в один ключ не упорядочены — два клиента «победят» одновременно, и появятся сиблинги.
  • Гонки чтения и записи: чтение в момент незавершённой записи может вернуть то старое, то новое значение.

Что R + W > N реально даёт

  • Чтение пересекается с последним завершённым строгим (strict) кворумом записи и видит свежую версию.
  • Контроль свежести и доступности per-запрос: двигаем R и W под задачу.
  • Базис для (read repair): расхождения видны прямо на чтении и чинятся.

Практический вывод: если нужна именно линеаризуемость (уникальность, банковский баланс, распределённая блокировка) — кворумы её не дают. Для этого Cassandra предлагает отдельный путь поверх Paxos, а в общем случае такие операции лучше отдать лидерскому консенсусу из соседней главы. Такие кворумы хороши там, где допустима .

Реальная система

Apache Cassandra

Прямой наследник Dynamo: настраиваемые уровни консистентности (ONE, QUORUM, ALL), временное хранение записи (hinted handoff), восстановление при чтении (read repair) и фоновая сверка (repair) на Merkle-деревьях.

Читать обзор

Системы: Dynamo, Cassandra, Riak

Идею задала статья Amazon Dynamo (DeCandia и др., SOSP 2007): не СУБД, а внутреннее хранилище «ключ — значение» (key-value), спроектированное под «всегда доступную запись» для корзин и сессий. Её приёмы — кольцо консистентного хеширования, нестрогий кворум (sloppy quorum), временное хранение записи (hinted handoff), векторные часы (vector clocks) и Merkle-деревья — стали шаблоном для целого класса систем.

СистемаЧто взяла у DynamoРазрешение конфликтовНастройка консистентности
Amazon Dynamo (2007)Кольцо, нестрогий кворум (sloppy quorum), временное хранение записи (hinted handoff), антиэнтропийная синхронизация (anti-entropy) с Merkle-деревьямиВекторные часы (vector clocks), сиблинги — слияние на стороне приложенияПараметры N/R/W задаются на уровне инстанса под профиль нагрузки
Apache CassandraКольцо, фактор репликации, временное хранение записи (hinted handoff), восстановление при чтении (read repair), фоновая сверка (repair)Стратегия «последняя запись побеждает» (LWW) по метке времени (timestamp) на уровне ячейки (cell), метки удаления (tombstone) на удалениеУровень консистентности на запрос: ONE, QUORUM, LOCAL_QUORUM, ALL
Riak KVКольцо, нестрогий кворум (sloppy quorum), временное хранение записи (hinted handoff), восстановление при чтении (read repair), активная антиэнтропийная синхронизация (active anti-entropy)Точечные векторы версий (dotted version vectors), сиблинги; опционально Riak Data Types — бесконфликтно реплицируемые типы данных (CRDT)n_val, r, w, pr/pw задаются на уровне корзины (bucket) и запроса

Важная развилка в разрешении конфликтов: Cassandra по умолчанию использует (LWW) (проще, но при рассинхронизации часов теряет данные), а Dynamo и Riak возвращают сиблинги и перекладывают смысл слияния на приложение. Riak пошёл дальше всех, встроив типы данных на основе (Riak Data Types), чтобы сливать конкурентные версии автоматически и корректно.

Компромиссы: когда выбирать топологию без выделенного ведущего узла (leaderless), а когда лидера

Топология без выделенного ведущего узла (leaderless) подходит, когда

  • Запись должна проходить при отказах узлов и сетевом разделении — доступность важнее свежести.
  • Нагрузка размазана по ключам, и узкое место в виде лидера нежелательно.
  • Кластер геораспределён, и постоянный кворумный раунд к лидеру в другом регионе слишком дорог.
  • Домен допускает и слияние конкурентных версий.

Лидер/консенсус лучше, когда

  • Нужна : уникальность, баланс, распределённая блокировка.
  • Операции должны выполняться в едином согласованном порядке (журнал команд, метаданные кластера).
  • Конкурентные записи в один ключ недопустимы или их слияние не имеет смысла.
  • Команда не готова сопровождать сиблинги, (read repair) и доменную логику слияния.

Частые ошибки

  • Ожидать линеаризуемости от R + W > N. Кворумное чтение свежее, но не линеаризуемо — особенно при sloppy quorum и конкурентных записях.
  • Доверять стратегии (LWW) там, где данные нельзя терять. Рассинхронизация часов превращает «последний победил» в тихую потерю правок.
  • Игнорировать сиблинги. Если приложение не умеет сливать конкурентные версии, они копятся и ломают логику.
  • Считать нестрогий (sloppy quorum) заменой строгого. Он повышает доступность, но снимает гарантию пересечения, на которую вы рассчитывали.

Что важно запомнить

  • В топологии без выделенного ведущего узла (leaderless) лидера нет: запись принимает любая реплика, а согласованность держится на арифметике кворумов, а не на координации.
  • Условие R + W > N гарантирует, что читающий набор пересечётся с пишущим и увидит свежую копию — но это свежесть кворумного чтения, а не линеаризуемость.
  • Нестрогий кворум (sloppy quorum) и временное хранение записи (hinted handoff) сохраняют доступность записи при отказах ценой ослабления гарантии пересечения; данные досылаются домой позже.
  • Восстановление при чтении (read repair) чинит расхождения на чтении, антиэнтропийная синхронизация (anti-entropy) с Merkle-деревьями — в фоне; вместе они дают отложенную согласованность.
  • Конкурентные записи обнаруживают векторы версий (version vectors), а разрешают стратегией «последняя запись побеждает» (LWW, с риском потери), сиблингами или бесконфликтно реплицируемыми типами данных (CRDT) из соседней главы.
  • Выбирайте топологию без выделенного ведущего узла (leaderless) ради доступности и устойчивости к разделению; для линеаризуемости берите лидерский консенсус.

Источники и материалы

Как читать список. Статья про Dynamo держит базовую модель репликации без выделенного ведущего узла (leaderless): нестрогий кворум (sloppy quorum), временное хранение записи (hinted handoff) и антиэнтропийную синхронизацию (anti-entropy) на Merkle-деревьях. Vogels даёт исторический контекст; книга DDIA — учебное объяснение компромиссов; документация Cassandra — детали реализации. Условие R+W>N полезно как модель пересечения кворумов, но линеаризуемости не обещает: её ломают нестрогий кворум (sloppy quorum), конкурентные записи, стратегия «последняя запись побеждает» (LWW) и рассинхронизация часов (clock skew).

Связанные главы

Чтобы отмечать прохождение, включи трекинг в Настройки