АрхитектураМикросервисы и интеграцииИнженер по распределённым системам

Как обеспечить пользователю видимость собственного изменения, если экран читает асинхронную проекцию?

Как обеспечить пользователю видимость собственного изменения, если экран читает асинхронную проекцию?

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

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

Нужно явно реализовать гарантию read-your-writes: после записи сохранить версию или позицию изменения и не считать запрос чтения завершённым, пока проекция не обработала это изменение. Практические варианты — временно читать данные из источника записи, дождаться нужной позиции в проекции или передать клиенту состояние «изменение ещё обрабатывается».

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

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

Асинхронные проекции появились как способ разделить модели записи и чтения. Источник записи может быть оптимизирован под проверку инвариантов, а отдельная проекция — под быстрые запросы, поиск или удобное представление данных.

Такое разделение снижает связанность и позволяет независимо масштабировать чтение, но вводит временную рассогласованность. Поэтому для пользовательских сценариев понадобились дополнительные гарантии, в частности read-your-writes.

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

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

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

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

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

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

Важно различать read-your-writes и полную согласованность. Первая гарантия относится к изменениям конкретного пользователя или сессии и не означает, что все пользователи немедленно увидят одинаковое состояние.

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

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

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

В системе управления подписками пользователь меняет тариф. Команда записи сразу подтверждает операцию, а экран подписки читает отдельную проекцию, обновляемую событиями. Иногда пользователь немедленно открывает экран и видит прежний тариф.

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

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

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

  1. Достаточно ли вернуть успешный ответ на запись, чтобы гарантировать актуальное чтение?

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

  1. Почему нельзя надёжно решить проблему ожиданием фиксированного времени?

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

  1. Можно ли использовать read-your-writes для любых связанных данных сразу?

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