Клиент сначала прочитал новую версию профиля, затем из-за переключения реплики увидел более старую. Какую гарантию согласованности должна обеспечить система, чтобы чтения этого клиента не откатывались назад?
Нужна гарантия монотонного чтения: после того как клиент увидел определённую версию данных, последующие чтения этого клиента не должны возвращать более старую версию. Для этого система должна учитывать уже наблюдённую клиентом версию и направлять чтение только на реплику, которая не отстаёт от неё, либо дождаться необходимого состояния.
Монотонные чтения стали важны из-за широкого применения асинхронной репликации. Она уменьшает задержку записи и позволяет продолжать работу при временной недоступности части реплик, но создаёт лаг: разные реплики могут находиться на разных версиях данных.
Без дополнительных гарантий клиент, перемещаясь между репликами, может наблюдать последовательность версий не по порядку. Даже если каждая отдельная реплика отвечает корректно, пользователь видит противоречивое поведение: данные сначала появились, а затем как будто исчезли.
Пусть клиент прочитал профиль в версии 42, а затем запрос попал на реплику, применившую только версию 39. Ответ второй реплики формально корректен относительно её локального состояния, но нарушает ожидание клиента о движении состояния вперёд.
Такое поведение особенно заметно после обновления страницы, переключения между узлами, работы через балансировщик или восстановления соединения. Неверное решение может привести к визуальному откату данных, повторному отображению устаревшего статуса или ошибочным действиям пользователя.
Важно отличать эту гарантию от линейризуемости. Монотонные чтения ограничивают порядок наблюдений одного клиента, но не требуют, чтобы все клиенты немедленно видели единую глобальную версию.
Система связывает с клиентской сессией некоторый маркер прогресса: например, номер версии, позицию журнала или логическую метку. После чтения версии 42 следующий запрос несёт требование увидеть состояние не старше версии 42.
Реплика может ответить только если достигла этой версии. Если она отстаёт, возможны три варианта: отправить запрос на более свежую реплику, подождать применения журнала до нужной позиции или вернуть ошибку временной недоступности вместо устаревшего результата.
Маркер прогресса должен сохраняться в рамках нужной области сессии. Если запросы клиента распределяются между независимыми сервисами, гарантию необходимо передавать через общий контекст запроса или другой согласованный механизм; одного постоянного сетевого соединения недостаточно.
Компромисс заключается в задержке и доступности. Ожидание отстающей реплики увеличивает latency, а требование свежей реплики может сделать чтение временно недоступным при сетевом разделении. Если бизнес-логика допускает устаревшие данные, можно ослабить гарантию, но тогда нельзя обещать отсутствие отката.
Гарантия также не исправляет уже показанный пользователю старый ответ и не делает запись атомарной для всех читателей. Она обеспечивает только неубывающий порядок наблюдений в рамках определённого клиента или сессии.
В сервисе управления заказами пользователь сначала видел статус «Оплачен», а после обновления страницы получал «Ожидает оплаты». Причина заключалась в том, что первый запрос обслуживала свежая реплика, а второй — реплика с задержкой применения событий.
Рассматривались три варианта. Синхронная репликация устраняла лаг чтения, но увеличивала задержку записи и зависимость от доступности реплик. Направление всех чтений на лидера давало более простое поведение, но создавало нагрузку на лидер и единую точку для чтений. Полностью игнорировать проблему было дешевле, но сохраняло пользовательские откаты.
Выбрали передачу клиенту позиции журнала, увиденной при чтении, и проверку этой позиции на следующей реплике. Если реплика не достигла требуемой позиции, маршрутизатор выбирал более свежую реплику; при отсутствии подходящей реплики запрос ненадолго ожидал или возвращал контролируемую ошибку.
В результате исчезли откаты внутри пользовательской сессии, а обычные чтения по-прежнему могли обслуживаться репликами. Цена решения — дополнительная служебная метка, усложнение маршрутизации и возможное увеличение задержки после переключения реплик.
Read-your-writes требует, чтобы клиент после собственной записи видел результат этой записи или более новое состояние. Монотонные чтения требуют более общего свойства: любое последующее чтение клиента не должно быть старше любого ранее увиденного им чтения, даже если клиент ничего не записывал.
Поэтому read-your-writes можно обеспечить специальной маршрутизацией после записи, а монотонные чтения должны учитывать прогресс после обычного чтения тоже. Гарантия read-your-writes не обязательно покрывает все переходы между версиями, наблюдаемыми клиентом.
Нет, это может быть достаточным только при дополнительных условиях. Если выбранная реплика не откатывает состояние и остаётся доступной, последовательность чтений на ней обычно не уменьшается, но при переключении на другую реплику гарантия снова требует проверки её версии.
Кроме того, реплика может быть восстановлена из более старого снимка, а балансировщик или сервисный слой может потерять привязку сессии. Надёжнее передавать и проверять явный маркер наблюдённого прогресса, а не полагаться только на закрепление маршрута.
Иногда — только ценой доступности или задержки. Если доступная реплика не достигла уже наблюдённой клиентом версии, система не может одновременно вернуть эту версию или более новую, продолжить работу через отстающую реплику и гарантировать отсутствие отката.
Практический выбор зависит от требований: можно ждать восстановления связи, отклонять запрос, направлять его на доступную свежую реплику или явно показывать состояние как недоступное. Возврат старых данных сохраняет доступность, но нарушает монотонные чтения.