АналитикаСистемный анализСистемный аналитик

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

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

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

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

Потребитель должен сравнивать номер версии или последовательности события с последней применённой версией этой сущности. Событие с версией не выше уже обработанной считается устаревшим или дубликатом и не применяется.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. Достаточно ли проверять только, что версия события больше последней сохранённой?

Нет, этого недостаточно для всех типов событий. Если событие описывает полный снимок состояния, можно применить более новую версию и перейти с версии 6 на версию 8. Если оно содержит только изменение поля или операцию над состоянием, пропуск версии 7 может сделать результат некорректным. В таком случае нужно обнаруживать разрыв, временно буферизовать события или запрашивать снимок.

  1. Как отличить устаревшее событие от события, которое просто пришло слишком рано?

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

  1. Можно ли использовать одну последовательность для всех сущностей?

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