АрхитектураПроектирование системАрхитектор распределённых систем

При сетевом разделении распределённого хранилища бизнес допускает временно устаревшее чтение. Какой системн...

При сетевом разделении распределённого хранилища бизнес допускает временно устаревшее чтение. Какой системный компромисс следует выбрать?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

Это решение не означает, что можно игнорировать конфликты записей или бизнес-инварианты. Для критичных операций, например списания денег, доступность отдельных операций может потребоваться временно пожертвовать ради корректности.

Исторический контекст

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

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

Постановка проблемы

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

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

Подробное решение

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

Контракт API обязан описывать гарантию явно. Например, после записи можно обещать только eventual consistency, а для отдельных операций — read-your-writes, когда клиент некоторое время читает не менее свежую версию, чем та, которую сам записал.

Для выбора версии обычно используют версии, логические часы или монотонные номера изменений. Однако механизм выбора версии сам по себе не решает бизнес-конфликт: если два узла приняли несовместимые изменения, потребуется правило слияния, отклонение одной операции или ручное разрешение.

Важно разделять операции. Некритичные данные, такие как счётчик просмотров или рекомендации, часто можно обновлять с сохранением доступности. Операции, нарушающие инвариант при параллельном выполнении, могут требовать кворумного подтверждения, единого лидера или отказа во время разделения. Это уменьшает доступность, но сохраняет корректность.

Следует также определить поведение после восстановления связи: как долго допустимо устаревание, что увидит клиент при конфликте, можно ли повторить запись и как система уведомит о неуспешном разрешении конфликта. Без этих правил «временная» несогласованность превращается в неопределённое поведение продукта.

Ситуация из практики

Сервис рекомендаций хранит пользовательские интересы в нескольких регионах. Во время разрыва связи между регионами команда рассмотрела два варианта: блокировать чтение до восстановления репликации или отдавать локальный снимок данных.

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

Выбрали локальное чтение с конечной согласованностью, версией записи и повторной синхронизацией после восстановления связи. Для настроек, влияющих на безопасность аккаунта, оставили отдельный строгий путь, который при недоступности источника возвращает ошибку вместо устаревшего значения. Так продукт сохранил доступность для рекомендаций, не распространив слабую гарантию на критичные данные.

Что кандидаты часто упускают

  1. Вопрос: Можно ли считать систему доступной, если она отвечает устаревшими данными?

    Ответ: Да, если её контракт допускает такую степень устаревания. В терминах CAP доступность означает, что каждый запрос к доступному узлу получает ответ, а не обязательно получает самое новое значение. Поэтому нужно отдельно указать гарантию свежести: eventual consistency, read-your-writes или более строгую модель.

  2. Вопрос: Достаточно ли выбрать конечную согласованность, чтобы безопасно принимать записи на всех узлах?

    Ответ: Нет. Конечная согласованность описывает сходимость состояний, но не гарантирует сохранение бизнес-инвариантов при конфликтующих записях. Для уникальности, денежных остатков и ограничений вместимости нужны дополнительные механизмы: сериализация, условная запись по версии, лидер, кворум или отказ операции при невозможности подтвердить состояние.

  3. Вопрос: Почему увеличение числа реплик само по себе не устраняет компромисс при сетевом разделении?

    Ответ: Реплики повышают отказоустойчивость и масштаб чтения, но не возвращают связь между изолированными группами узлов. Во время разделения одна группа либо отвечает локально и рискует вернуть устаревшие или конфликтующие данные, либо ждёт недоступную группу и теряет доступность. Репликация помогает восстановиться после сбоя, но не позволяет одновременно гарантировать свежесть и успешный ответ во время самого разделения.