Согласованность в распределённой системе: что ломается первым
Под нагрузкой распределённая система не просто становится медленнее: она начинает выбирать, какие гарантии нарушить. Разбираем, как конфликтуют согласованность, доступность и задержка, что ломается первым и какие метрики это покажут.
При росте нагрузки распределённая система обычно теряет не всё сразу. Сначала растёт хвост задержек, затем заканчиваются места в очередях, после этого появляются тайм-ауты, устаревшие чтения или отказы записи. Линейризуемость можно сохранить ценой доступности, а доступность — ценой временно разных ответов на разных узлах. Ниже — разбор того, как выбрать этот компромисс и как понять по метрикам, какая гарантия уже принесена в жертву.
Что именно называют согласованностью
Согласованность — не одно универсальное свойство. В распределённой системе важно заранее назвать гарантию, иначе команда спорит о разных вещах одним словом.
- Линейризуемость — операция выглядит так, будто выполняется мгновенно в единой точке времени. После успешной записи следующий клиент не должен увидеть старое значение.
- Последовательная согласованность — операции каждого клиента сохраняют свой порядок, но глобальный порядок может отличаться от реального времени.
- Причинная согласованность — если одно событие стало причиной другого, наблюдатели не должны увидеть их в обратном порядке.
- Согласованность в конечном счёте — после прекращения новых изменений копии со временем сходятся, но период расхождения не фиксирован.
- Сериализуемость — параллельные транзакции дают результат, эквивалентный некоторому последовательному выполнению. Это свойство транзакций, а не всей сети.
Например, каталог товаров может пережить чтение цены на несколько секунд старее. Баланс счёта, остаток единственного билета или право пользователя на действие — обычно нет. Ошибка начинается, когда для всех данных выбирают одну и ту же гарантию.
Сноска. Линейризуемость — гарантия, при которой успешная операция записи становится видимой как единый атомарный момент между её началом и завершением. Это строже, чем простое требование «реплики когда-нибудь синхронизируются».
Что ломается первым под нагрузкой
В типичной системе первым страдает не сама согласованность, а время ожидания. Строгая запись должна дождаться подтверждений нужного числа узлов, а чтение с гарантией актуальности — обратиться к источнику, который знает, что его состояние не устарело. При потере узла или росте сетевой задержки этот путь удлиняется.
1. Хвост задержек
Среднее время ответа может выглядеть приемлемо, пока p95 и p99 уже растут. Небольшая доля запросов попадает на медленную реплику, ждёт кворум или повторяет сетевой вызов. Именно она запускает цепочку тайм-аутов: клиент держит соединение, занимает поток, а новые запросы встают в очередь.
2. Очереди и тайм-ауты
Когда время обработки превышает интервал между запросами, очередь растёт быстрее, чем система успевает её разгружать. Тайм-аут на клиенте не отменяет работу на сервере автоматически. Повторная попытка может удвоить нагрузку и превратить единичный сбой в шторм запросов.
3. Доступность записи
Если для записи нужен кворум, потеря части узлов или сетевой раздел может сделать запись невозможной. Это не обязательно ошибка реализации. Система сознательно отказывается подтверждать операцию, если не может доказать, что сохранит выбранную гарантию.
4. Свежесть чтения
Если чтение разрешено с любой реплики, ответ может быть старым. Если чтение перенаправляется на лидера или требует проверки версии, цена — дополнительная задержка и зависимость от доступности лидера. На этом этапе «сервис доступен» и «сервис возвращает актуальные данные» становятся разными утверждениями.
Под нагрузкой вы выбираете не между быстрым и медленным сервисом. Вы выбираете, что пользователь увидит при задержке: отказ, старое значение или результат, который нужно откатить позже.
Четыре стратегии и их цена
Ниже — не продукты, а архитектурные режимы. В реальной системе они могут сосуществовать: например, платежи работают через строгий кворум, а счётчик просмотров — через асинхронную репликацию.
| Подход | Что получает пользователь | Что ломается первым | Когда оправдан |
|---|---|---|---|
| Синхронный кворум | Запись подтверждается после согласия достаточного числа узлов | Доступность и хвост задержек при потере узлов | Баланс, права, уникальные ресурсы |
| Лидер и чтение с лидера | Простой порядок записи и свежие чтения из одного источника | Производительность и доступность при проблемах лидера | Состояния, где важен порядок изменений |
| Асинхронные реплики | Низкая задержка записи и чтения, больше доступных узлов | Свежесть данных и риск потери последних изменений | Каталог, ленты, аналитические витрины |
| Оптимистическая запись с версиями | Параллельные изменения принимаются без блокировки | Конфликты при сохранении и необходимость повторной операции | Профили, документы, настройки с разрешимым конфликтом |
Кворум сам по себе не делает систему линейризуемой. Важно, как выбираются версии, кто считается лидером, что происходит при разделении сети и когда операция считается подтверждённой. Неправильно настроенный обмен «большинство узлов ответило» может сохранить доступность, но не дать нужного порядка чтений.
Как отличить нехватку ресурсов от конфликта гарантий
Одинаковый симптом — рост времени ответа — возникает по разным причинам. Перед изменением архитектуры зафиксируйте один сценарий: размер запроса, долю чтений и записей, число клиентов, распределение по ключам, число узлов, сетевую задержку и длительность теста.
- Снимите базовую линию. Запишите throughput, p50, p95 и p99, долю ошибок, тайм-ауты и длину очередей. Без этого «после оптимизации» нечем сравнивать.
- Разделите типы операций. Отдельно измерьте чтение с гарантией свежести, чтение с реплики и запись. Средняя цифра по всему API скрывает, какая именно операция стала узким местом.
- Добавьте отказ одного узла. Проверьте, сохраняется ли запись, что видит чтение и как меняется p99. Делайте это на стенде или в окне, где предусмотрен откат.
- Смоделируйте задержку между узлами. Измерьте момент, когда строгие операции начинают истекать по тайм-ауту. Так вы увидите запас до отказа, а не только поведение в спокойном режиме.
- Проверьте повторную доставку. Один и тот же запрос после тайм-аута должен либо безопасно повторяться, либо иметь защиту от двойного применения. Иначе авария сети превращается в бизнес-дубликаты.
Для систем на PostgreSQL 16 отдельно смотрите блокировки, ожидания транзакций и задержку репликации. Для etcd 3.5 важны состояние кластера, задержка подтверждения предложений и ошибки кворума. Эти показатели нельзя смешивать с задержкой HTTP: приложение может отвечать медленно из-за пула соединений, даже если хранилище работает нормально.
Когда какой компромисс выбирать
Строгая согласованность
Выбирайте её, если неверное чтение приводит к финансовой операции, нарушению доступа или выдаче одного ресурса двум клиентам. Цена — отказ в момент, когда система не может подтвердить состояние. Пользователь должен получить понятную ошибку и возможность безопасно повторить действие.
Асинхронная репликация
Она подходит, когда устаревший ответ дешевле отказа. Но задайте предел: насколько старые данные допустимы, как отображается задержка и что происходит при потере лидера. Формулировка «данные Eventually Consistent» без такого предела не является эксплуатационным требованием.
Оптимистическое разрешение конфликтов
Этот вариант уместен, если конфликт можно показать пользователю или автоматически объединить. Версия объекта должна участвовать в проверке сохранения. Иначе два клиента прочитают одно состояние, оба успешно запишут изменения, а последнее обновление молча затрёт первое.
Где подход ломается
- Повтор после тайм-аута. Клиент не знает, применился ли запрос. Для денежных и иных необратимых операций нужны идемпотентный ключ или проверка результата по идентификатору операции.
- «Свежий» кэш. Инвалидация кэша может прийти позже записи. Короткий TTL не гарантирует строгую актуальность, а удаление ключа не отменяет уже выданный ответ.
- Часы и порядок событий. Временная метка машины не доказывает порядок операций между узлами. Для причинности нужны версии, последовательности или механизм консенсуса.
- Сетевой раздел. Если обе части продолжают принимать конфликтующие записи, после восстановления придётся выбирать победителя или объединять версии. Автоматическое «схождение» не означает сохранение бизнес-смысла.
- Горячий ключ. Даже при большом количестве узлов один популярный объект может стать точкой сериализации. Масштабирование количества реплик не устранит конфликт за одну запись.
Не пытайтесь лечить все эти случаи увеличением тайм-аута. Больший тайм-аут снижает долю быстрых ошибок, но дольше удерживает ресурсы и часто ухудшает восстановление. Сначала определите, какая гарантия нужна конкретной операции, затем ограничьте время ожидания и опишите безопасный сценарий повтора.
Что делать дальше
Составьте таблицу операций: для каждой укажите допустимую свежесть, допустимую потерю, необходимость уникальности и поведение при сетевом разделе. После этого добавьте дашборд с p95/p99, задержкой репликации, долей тайм-аутов, длиной очередей и количеством повторов.
Следующий практический шаг — тест отказа одного узла на копии production-нагрузки. Сравните три режима: чтение с лидера, чтение с реплики и чтение с проверкой версии. Не ищите одну «правильную» согласованность для всей системы. Ищите границу, за которой конкретная бизнес-операция уже не может принимать старые или конфликтующие данные.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: etcd 3.5: API guarantees, PostgreSQL 16: Transaction Isolation
Частые вопросы
Всегда ли строгая согласованность лучше конечной?
Почему система отвечает медленно, хотя CPU и память не исчерпаны?
Можно ли включить чтение с реплик без риска?
Что произойдёт, если запрос получил тайм-аут, но всё же выполнился?
Как понять, что проблема именно в сетевом разделе?
Нужно ли проверять согласованность в нагрузочном тесте?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.