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

Когда база данных разделена между несколькими машинами (репликами), вы получаете скорость и устойчивость — но при этом возникают периоды, когда эти машины не полностью согласованы или не могут надёжно общаться друг с другом.
Согласованность означает: после успешной записи все дальнейшие чтения возвращают одно и то же значение. Если вы обновили email в профиле, следующее чтение — независимо от того, к какой реплике вы обратились — возвращает новый адрес.
На практике системы, которые отдают приоритет согласованности, могут задерживать или отклонять некоторые запросы во время сбоев, чтобы не возвращать конфликтующие ответы.
Доступность означает: система отвечает на каждый запрос, даже если некоторые серверы упали или отключены. Вы можете не получить самые свежие данные, но вы получите ответ.
На практике системы, ориентированные на доступность, могут принимать записи и обслуживать чтения даже когда реплики рассогласованы, а затем синхронизировать различия позже.
«Компромисс» значит, что вы не можете одновременно максимизировать обе цели во всех сценариях сбоев. Если реплики не могут координироваться, база данных должна либо:
Правильный баланс зависит от того, какие ошибки вы готовы терпеть: кратковременный простой или кратковременное неверное/устаревшее состояние. Большинство систем выбирают компромисс и явно документируют его.
База данных «распределена», когда она хранит и обслуживает данные с нескольких машин (узлов), которые координируются по сети. Для приложения это всё ещё может выглядеть как одна база — но под капотом запросы могут обслуживаться разными узлами в разных местах.
Большинство распределённых баз реплицируют данные: одна и та же запись хранится на нескольких узлах. Это делают чтобы:
Репликация мощна, но сразу встает вопрос: если две ноды имеют копию одних и тех же данных, как гарантировать, что они всегда согласятся?
На одной машине «вниз» обычно очевидно: машина либо работает, либо нет. В распределённой системе отказ часто частичный. Одна нода может быть жива, но медленна. Сетевой канал может терять пакеты. Целый стэк может потерять связь, в то время как остальная кластер продолжает работать.
Это важно, потому что узлы не могут мгновенно узнать, действительно ли другой узел упал, временно недоступен или просто задерживается. Пока они решают, что делать, им всё равно нужно принимать решение по входящим чтениям и записям.
На одной сервере есть один источник истины: каждое чтение видит последнюю успешную запись.
На нескольких узлах «последнее» зависит от координации. Если запись прошла на узле A, но узел B недоступен, должна ли база:
Это напряжение — результат ненадёжных сетей — и именно поэтому распределение меняет правила.
Сетевой разрыв — это нарушение связи между узлами, которые должны работать как единая база. Узлы могут продолжать быть работоспособными, но не могут надёжно обмениваться сообщениями — из-за сломанного свитча, перегруженного канала, ошибки маршрутизации, неверного правила файервола или даже «шумного» соседа в облаке.
Когда система распространена по множеству машин (часто по стойкам, зонам или регионам), вы уже не контролируете каждый шаг между ними. Сети теряют пакеты, добавляют задержки и иногда разрываются на «острова». На малых масштабах такие события редки; в большом масштабе они регулярны. Даже короткое нарушение важно, потому что базе нужно постоянное согласование, чтобы понять, что произошло.
Во время разрыва обе стороны продолжают получать запросы. Если пользователи пишут на обеих сторонах, каждая может принять обновление, которое другая не увидит.
Пример: узел A обновил адрес пользователя на «Новая улица». В то же время узел B обновил его на «Старая улица, кв. 2». Каждая сторона считает свою запись самой свежей — потому что нет способа в реальном времени свериться.
Разрывы не появляются в виде аккуратных сообщений об ошибке; они проявляются как сбивающее с толку поведение:
Это именно то место, где принимается решение: когда сеть не может гарантировать связь, распределённая база должна выбрать — приоритезировать согласованность или доступность.
CAP — краткий способ описать, что происходит, когда база распространяется на множество машин.
Когда нет разрыва, многие системы выглядят и согласованными, и доступными.
Когда есть разрыв, нужно выбрать, что приоритетно:
balance = 100 на сервер A.balance = 80.CAP не говорит «навсегда выбирай два». Он говорит: во время разрыва вы не можете одновременно обеспечить и Согласованность, и Доступность. Вне разрывов многие системы близки к обеим целям — до следующего сетевого инцидента.
Выбирая согласованность, система отдаёт приоритет тому, чтобы «все видели одну правду», в ущерб тому, чтобы «всегда отвечать». На практике это обычно означает сильную согласованность, часто называемую линеаризуемостью: после подтверждённой записи любое последующее чтение (из любой реплики) возвращает это значение, как если бы был единственный актуальный копия.
Когда сеть разделяется и реплики не могут координироваться, сильно согласованная система не сможет безопасно принимать независимые обновления с обеих сторон. Чтобы защитить корректность, она обычно:
С точки зрения пользователя, это может выглядеть как простой, хотя некоторые машины всё ещё работают.
Главное преимущество — проще рассуждать о поведении. Код приложения может вести себя так, будто он общается с одной базой, а не с набором реплик, которые могут расходиться. Это уменьшает «странные моменты», такие как:
Также это облегчает аудит, биллинг и другие сценарии, где важна корректность с первого раза.
Согласованность имеет реальные издержки:
Если продукт не терпит отказов запросов во время частичных сбоев, сильная согласованность может показаться дорогой — даже если она и обеспечивает корректность.
Выбирая доступность, вы оптимизируете под простое обещание: система отвечает, даже когда часть инфраструктуры больна. «Высокая доступность» не означает «никаких ошибок совсем» — это значит, что большинство запросов всё ещё получают ответ во время падения нод, перегрузок или разрывов сети.
Когда сеть разделяется, реплики не могут уверенно общаться. Система, ориентированная на доступность, обычно продолжает обслуживать трафик с той части, до которой можно достучаться:
Это позволяет приложениям продолжать работу, но значит, что разные реплики могут временно принимать разные «правды».
Вы получаете лучшую доступность: пользователи всё ещё могут просматривать товары, добавлять в корзину, оставлять комментарии или записывать события, даже если регион изолирован.
Также более плавный пользовательский опыт при нагрузке. Вместо таймаутов приложение может продолжать работать («обновление сохранено») и синхронизироваться позже. Для многих потребительских и аналитических нагрузок такой компромисс оправдан.
Цена — база может возвращать устаревшие чтения. Пользователь обновил профиль на одной реплике, затем сразу прочитал с другой и не увидел изменения.
Также риск конфликтов записей. Два пользователя (или один и тот же в разных местах) могут обновить одну и ту же запись на разных сторонах разрыва. При восстановлении система должна будет разрешать расхождения. В зависимости от правил одна запись может «выиграть», поля могут слиться, либо потребуется логика приложения.
Дизайн в пользу доступности принимает временные расхождения и вкладывается в механизмы обнаружения и исправления потом.
Кворумы — практическая техника голосования, которую многие реплицированные базы используют для балансирования согласованности и доступности. Вместо того чтобы полагаться на одну реплику, система требует согласия «достаточного» числа реплик.
Кворумы часто описывают тремя числами:
Общее правило: если R + W > N, то каждое чтение пересекается с последней успешной записью хотя бы на одной реплике, что снижает шанс чтения устаревших данных.
Если у вас N=3 реплики:
Некоторые системы требуют W=3 (всем репликам) для более сильной согласованности, но это повышает риск отказов записи при медленной или недоступной реплике.
Кворумы не устраняют проблему разрывов — они определяют, кто может продолжать работу. Если сеть разделилась 2–1, сторона с 2 репликами может удовлетворить R=2 и W=2, а изолированная одиночная реплика не сможет. Это уменьшает количество конфликтующих обновлений, но означает, что часть клиентов может видеть ошибки или таймауты.
Кворумы обычно дают большую задержку (надо связаться с несколькими узлами), больший стоимость (межузловой трафик) и более сложное поведение при отказах (таймауты выглядят как недоступность). Зато вы получаете регулируемую среднюю линию: можно настроить R и W ближе к свежим чтениям или к успешным записям, в зависимости от приоритетов.
Отложенная (eventual) согласованность означает, что реплики временно могут быть не в согласии, при условии что со временем они сойдутся к одному значению.
Представьте сеть кофеен, обновляющих общий знак «распродано» для булочки. Один магазин пометил «распродано», но обновление добирается до других магазинов через пару минут. В промежутке другой магазин всё ещё показывает «в наличии» и может продать последний экземпляр. Система не «сломана» — просто обновления догоняют.
Когда данные ещё реплицируются, клиенты могут заметить неожиданные поведения:
Системы с отложенной согласованностью часто применяют фоновые механизмы:
Подходит, когда важнее доступность, чем абсолютная актуальность: ленты активности, счётчики просмотров, рекомендации, кеши/профили, логи/телеметрия и другие данные, где «правильно через момент» — приемлемо.
Когда база принимает записи на нескольких репликах, могут возникать конфликты: два или более обновления одного и того же элемента, сделанные независимо на разных репликах до синхронизации.
Классический пример: пользователь меняет адрес доставки на одном устройстве и номер телефона на другом. Если каждое обновление попало на разную реплику во время разрыва, система должна решить, что считать «истиной» после обмена данными.
Многие системы начинают с last-write-wins: обновление с самым свежим timestamp перетирает остальные.
Привлекает простотой и скоростью вычисления. Минус в том, что оно может тихо потерять данные. «Новее» побеждает, и тогда более старое, но важное изменение может быть утеряно — даже если обновления затрагивали разные поля.
Кроме того, это предполагает доверие к часам. Смещение времени между машинами (clock skew) может привести к тому, что «неправильное» обновление победит.
Более безопасная обработка конфликтов обычно требует отслеживания причинно-следственной истории.
Концептуально векторы версий (version vectors) добавляют небольшой фрагмент метаданных к каждой записи, который резюмирует «какая реплика видела какие обновления». При обмене версиями база может определить, одна версия включает другую (нет конфликта), или они разошлись (нужна резолюция).
Некоторые системы применяют логические метки времени (например, часы Лампорта) или гибридные логические часы, чтобы меньше полагаться на wall-clock, но всё ещё иметь подсказку порядка.
Когда конфликт обнаружен, есть варианты:
Лучший подход зависит от того, что значит «правильно» для ваших данных — иногда потеря записи допустима, а иногда это критическая ошибка.
Выбор позы согласованности/доступности — это не философский спор, а продуктовый выбор. Начните с вопроса: какова цена ошибки на мгновение, и какова цена ответа «повторите позже»?
Некоторые домены требуют единого авторитетного ответа при записи, потому что «почти правильно» — всё ещё неправильно:
Если влияние временного рассогласования невелико или обратимо, можно чаще отдавать приоритет доступности.
Многие пользовательские сценарии нормально работают со слегка устаревшими чтениями:
Будьте конкретны: сколько устаревания допустимо — секунды, минуты или часы. Этот бюджет времени определяет ваши выборы репликации и кворума.
Когда реплики не могут договориться, обычно получается один из трёх UX-исходов:
Выбирайте наименее вредный вариант для каждой функции, а не глобально для всей системы.
Склоняйтесь к C (согласованности) если: неправильный результат создаёт финансовый/юридический риск, угрозу безопасности или необратимые действия.
Склоняйтесь к A (доступности) если: пользователи ценят отзывчивость, устаревшие данные допустимы, и конфликты можно безопасно разрешить позже.
Если не уверены, раздельте систему: храните критичные записи с сильной согласованностью, а производные представления (фиды, кеши, аналитика) оптимизируйте под доступность.
Вам редко нужно выбирать одну «настройку согласованности» для всей системы. Многие современные распределённые базы позволяют выбирать уровень согласованности на операцию — и умные приложения используют это, чтобы сохранить UX без иллюзии о том, что компромисса не существует.
Относитесь к согласованности как к регулятору, который вы настраиваете в зависимости от действия пользователя:
Так вы не платите высокой ценой согласованности за всё подряд, но защищаете важные операции.
Обычный паттерн: сильные записи, слабые чтения:
Иногда наоборот: быстрые записи (очередные/отложенные) + сильные чтения при подтверждении результата ("Заказ оформлен?").
Когда сети шатаются, клиенты повторяют запросы. Делайте повторы безопасными с помощью idempotency keys, чтобы «отправить заказ» дважды не создало два заказа. Храните и повторно используйте первый результат, когда видите тот же ключ.
Для многозадачных действий через сервисы используйте сагу: каждый шаг имеет соответствующее компенсирующее действие (возврат денег, освобождение резерва, отмена доставки). Это делает систему восстанавливаемой, даже когда части временно рассогласованы или падают.
Вы не сможете управлять компромиссом, если не видите его. Проблемы в продакшене часто выглядят как «случайные отказы», пока вы не добавите нужные метрики и тесты.
Начните с небольшой группы метрик, которые напрямую отражают влияние на пользователя:
Если возможно, тэгируйте метрики по режиму согласованности (кворум vs локал) и по региону/зоне, чтобы заметить, где поведение расходится.
Не ждите реального сбоя. В стейджинге проводите хаос-эксперименты, симулируя:
Проверяйте не просто «система остаётся в сети», а какие гарантии сохраняются: остаются ли чтения свежими, блокируются ли записи, получают ли клиенты понятные ошибки?
Добавьте оповещения для:
Наконец, делайте гарантии явными: документируйте, что ваша система обещает в норме и при разрывах, и обучайте команды продукта и поддержки, что пользователи могут увидеть и как реагировать.
Если вы исследуете эти компромиссы в новом продукте, полезно валидировать предположения рано — особенно про режимы отказа, поведение повторов и как выглядит «устаревшее» в UI.
Практический подход — прототипировать небольшой вариант рабочего процесса (путь записи, путь чтения, повторы/идемпотентность и джоб по согласованию) до полной архитектуры. С помощью инструментов для быстрой разработки команд могут быстро собрать веб-приложение и бэкенд, пробовать разные паттерны согласованности (например, строгие записи + ослабленные чтения) и оценить UX без больших затрат. Когда прототип соответствует нужному поведению, его можно развивать в продакшн.
В реплицируемой базе данных «одни и те же» данные живут на нескольких машинах. Это повышает устойчивость и может снизить задержки, но приводит к проблемам координации: узлы бывают медленными, недоступными или разделёнными сетью, поэтому они не всегда могут мгновенно согласовать последний запись.
Согласованность означает: после успешной записи любая последующая операция чтения возвращает это же значение — независимо от того, какой реплики вы попали. На практике системы часто добиваются этого, задерживая или отклоняя чтения/записи до тех пор, пока достаточное число реплик (или лидер) не подтвердят обновление.
Доступность означает, что система возвращает не-ошибочный ответ на каждый запрос, даже когда некоторые узлы упали или не могут общаться. Ответ может быть устаревшим, частичным или основанным на локальном состоянии, но система избегает блокировок пользователей во время сбоев.
Сетевой разрыв — это разрыв связи между узлами, которые должны работать как единая система. Узлы могут оставаться работоспособными, но сообщения надёжно не проходят через разрыв, и база вынуждена выбирать между:
Во время разрыва обе стороны могут принимать обновления, которые не видны другой стороне. Это может привести к:
Это — видимые пользователю последствия временной неспособности реплик согласовываться.
Это не значит «навсегда выбирай два из трёх». Это значит: когда происходит разрыв сети, вы не можете одновременно гарантировать:
Вне разрывов многие системы выглядят как согласованные и доступные одновременно — до тех пор, пока сеть не начнёт вести себя плохо.
Кворумы реализуют голосование между репликами:
Частое правило: R + W > N, чтобы чтение перекрывалось с хотя бы одной репликой, где записывалась последняя успешная запись. Кворумы не устраняют разрывы, но определяют, какая сторона может продолжать прогресс (например, та, где есть большинство).
В конечном итоге согласованность допускает временное рассогласование реплик, если они со временем сходятся к одному значению. Типичные аномалии:
Для уменьшения окна рассогласования используют , и периодическую синхронизацию.
Конфликты появляются, когда разные реплики принимают разные записи для одного и того же элемента во время разрыва. Стратегии разрешения:
Выбор зависит от того, что считается «правильным» для ваших данных.
Решение опирается на оценку бизнес-риска и какой режим отказа пользователи переносят:
Практики: уровни согласованности на уровне операции, идемпотентные повторы (idempotency), и саги/компенсации для длинных многосервисных транзакций.