Согласованность в распределённой системе: что придётся отдать
Линейризуемость, доступность и устойчивость к разделению сети нельзя получить одновременно без ограничений. Разбираем, какие гарантии согласованности существуют, чем они оплачиваются и что измерять перед выбором архитектуры.

Сервис может продолжать принимать заказы при разрыве связи между датацентрами, но тогда часть клиентов увидит разные остатки на складе. Можно сохранить единую картину данных, но придётся отклонять записи или ждать восстановления сети. В распределённой системе согласованность — это не переключатель «включить или выключить», а набор гарантий с конкретной ценой по задержке, доступности, сложности восстановления и риску потери свежих данных.
Сначала определите, что именно должно быть согласованным
Слово «согласованность» скрывает несколько разных требований. Для каталога товаров достаточно, чтобы изменения обычно доходили до реплик за секунды. Для списания денег или выдачи уникального логина нужна гарантия, что две операции не подтвердят несовместимый результат.
Полезно описывать требование через наблюдаемое поведение, а не через название модели:
- какие записи должны быть видны всем узлам одинаково;
- как быстро новое значение обязано появиться после записи;
- может ли система принять операцию без связи с другими узлами;
- что произойдёт при повторной отправке запроса;
- допустимо ли временно показать устаревшие данные.
Линейризуемость1 означает, что каждая операция выглядит так, будто произошла в одной точке времени между вызовом и ответом. Это сильная гарантия, но она не равна полной согласованности транзакций: отдельно нужно определить границы транзакции, порядок операций и правила отката.
Нельзя обещать пользователю «данные всегда одинаковые» и одновременно требовать «каждый узел принимает запись при любой сетевой аварии». Одно из условий придётся ослабить или ограничить область действия.
Главный конфликт: разделение сети
CAP-теорема рассматривает систему, которая должна продолжать работу при разделении сети между узлами. В этот момент нельзя одновременно гарантировать линейризуемость и доступность для всех запросов. Это не означает, что в любой системе нужно выбрать только два пункта из трёх: сеть в распределённой системе всё равно может разделиться, а теорема описывает поведение именно в такой аварии.
Если выбрать согласованность
Узел не подтверждает запись, пока не получит нужные подтверждения от кворума или лидера. При потере связи часть запросов получает ошибку или ждёт тайм-аута. Цена выбора — задержка между регионами, зависимость от большинства узлов и снижение доступности во время сетевого разделения.
Такой режим нужен там, где неправильный результат дороже отказа в обслуживании: уникальные номера платежей, блокировки, остатки ограниченного товара, права доступа. Важно, чтобы бизнес переживал отказ корректно: пользователь должен увидеть понятное «операция не завершена», а не повторно списать деньги после тайм-аута.
Если выбрать доступность
Каждая сторона разделённой системы продолжает принимать локальные записи. Когда связь вернётся, данные нужно объединить. Цена — временные расхождения, конфликты и необходимость определить победителя или применить правила слияния.
Этот вариант подходит для лайков, просмотров, черновиков, части профиля и других данных, где короткий период расхождения допустим. Он опасен для операций, которые нельзя безопасно применить дважды или выполнить в двух независимых местах.
Какие модели встречаются на практике
| Модель | Что видит читатель | Цена | Типичный сценарий |
|---|---|---|---|
| Линейризуемость | Последовательные чтения видят подтверждённые записи в едином порядке | Отказы или ожидание при потере кворума, более высокая задержка | Баланс, уникальные ресурсы, критические блокировки |
| Последовательная согласованность | Операции каждого клиента сохраняют порядок, общий порядок может отличаться от реального времени | Нужно координировать порядок между участниками | События, совместная работа с ограниченными требованиями |
| Причинная согласованность | Причинно связанные изменения не меняются местами | Сложнее переносить контекст между запросами и узлами | Комментарии, ленты, уведомления |
| Чтение после записи | Пользователь после своей записи видит не более старую версию | Гарантия может действовать только для конкретного пользователя или сессии | Профиль, настройки, форма заказа |
| Eventual consistency | После прекращения новых изменений реплики со временем сходятся | Окно устаревших данных и отдельная обработка конфликтов | Кеши, поисковые индексы, рекомендации |
Последняя модель не обещает конкретный срок сходимости сама по себе. Если реплика постоянно отстаёт, очередь не разгружается или сообщения теряются, «eventual» не превращается в гарантию за пять секунд. Срок нужно задавать отдельно через SLO и контролировать метриками.
Кворум не решает всё
В системах с репликацией часто используют запись и чтение через кворумы. Упрощённая интуиция такова: если для записи нужно подтверждение от W реплик, а для чтения — от R, пересечение наборов появляется при условии R + W > N, где N — число реплик. Но этого недостаточно, чтобы автоматически получить линейризуемость.
Нужно проверить, как система выбирает версии, что происходит при одновременных записях, сохраняется ли порядок, как устроено восстановление после сбоя и может ли чтение обратиться к устаревшему узлу. В некоторых реализациях кворум означает число ответивших реплик, но не гарантирует, что эти реплики имеют одинаковую версию или что конфликт будет разрешён в пользу более нового значения.
Цена репликации между регионами
Кворум, растянутый между регионами, платит межрегиональной задержкой и зависит от доступности каналов. Кворум внутри одного региона отвечает быстрее, но при аварии региона может не сохранить доступ к большинству. Разумный выбор зависит от того, что важнее: пережить потерю региона без остановки записи или держать задержку в пределах пользовательского SLO.
Не смешивайте синхронную репликацию, резервное копирование и доставку событий. Бэкап помогает восстановить данные после разрушительного сбоя, но не делает чтение из приложения согласованным. Очередь событий переносит изменения, но сама по себе не описывает единую транзакцию между сервисами.
Где решение ломается первым
- Повтор запроса после тайм-аута. Клиент не знает, была ли запись применена. Нужен идемпотентный идентификатор операции или другой способ безопасно распознать повтор.
- Две независимые записи одного ресурса. Last-write-wins может молча затереть корректное изменение. Для счётчиков, запасов и балансов требуются операции, учитывающие конкуренцию, а не простая замена значения.
- Кеш рядом с сильной базой. База могла уже принять новую версию, а кеш продолжает отдавать старую. Политика инвалидирования и допустимое окно устаревания должны быть частью контракта.
- Долгоживущая очередь. При задержке доставки система выглядит доступной, но вторичные представления расходятся. Измеряйте возраст самого старого необработанного события, а не только размер очереди.
- Восстановление после разделения. Самый сложный момент — не отказ, а слияние независимых изменений. Правило разрешения конфликтов нужно тестировать на реальных сценариях до запуска.
Что измерять вместо обещания «данные почти сразу синхронны»
Для каждой критичной операции задайте наблюдаемые границы. Например: доля чтений, которые видят устаревшую версию; максимальный возраст реплики; время от записи события до обновления проекции; количество конфликтов при слиянии; доля повторных операций, завершившихся безопасно.
Отдельно измеряйте поведение во время отказа. Проверьте потерю связи между регионами, недоступность лидера, задержку доставки событий и восстановление после возвращения узла. Снимок «всё работает» не показывает, какую цену система платит именно в момент, ради которого выбирали распределённую архитектуру.
Как выбрать гарантию для нового сервиса
- Разделите данные по критичности: деньги и права доступа отдельно от поискового индекса и рекомендаций.
- Запишите допустимое устаревание в секундах или явно укажите, что оно недопустимо.
- Определите поведение при тайм-ауте: повтор, ошибка, отложенная обработка или ручная проверка.
- Назначьте владельца конфликтов: база, доменный сервис, очередь или оператор.
- Проведите отказной тест и сравните результат с SLO, а не с ожиданием команды.
Не пытайтесь выбрать одну модель для всех таблиц и потоков. В одном сервисе запись платежа может требовать сильной гарантии, а обновление поискового индекса — принимать задержку. Чем точнее границы, тем меньше координации и тем дешевле система.
Что делать дальше
Начните с таблицы инвариантов: какие условия нельзя нарушить ни при каком сценарии, а какие данные могут устареть. Затем добавьте в тестовый стенд сетевые разделения и задержки, измерьте возраст реплик, время восстановления и число конфликтов. После этого закрепите выбранные гарантии в API-контракте: клиенту должно быть понятно, когда операция окончена, когда её нужно повторить и какой результат считается допустимым.
1 Линейризуемость — гарантия, при которой каждая операция выглядит атомарной и занимает место в общем порядке между моментом вызова и моментом ответа.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Brewer’s Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services, Dynamo: Amazon’s Highly Available Key-value Store
Частые вопросы
Можно ли получить одновременно согласованность, доступность и устойчивость к разделению сети?
Подходит ли eventual consistency для интернет-магазина?
Если кворум записей больше половины, данные точно согласованы?
Что произойдёт, если клиент повторит запрос после тайм-аута?
Нужно ли использовать одну модель согласованности для всего сервиса?
Что делать, если после восстановления сети появились конфликты?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.