Live feed редко гарантирует идеальный порядок при реальных reconnect и corrections. Поэтому потребитель должен уметь отличить «сообщение пришло позже» от «состояние действительно стало новее». Простое правило last-write-wins здесь опасно.
Dedup начинается с устойчивого ключа
Если провайдер даёт event ID или dedup key, обработчик должен быть идемпотентным. Повторная доставка не создаёт второе событие и не запускает зависимые side effects заново. Когда ключа нет, его приходится строить из контекста и отдельно документировать риск коллизий.
Sequence помогает, но не заменяет правила конфликтов
Версия или порядковый номер позволяют обнаружить пропуск и позднее сообщение. Без такой информации нужен ограниченный reorder window и явные правила для competing updates. В обоих случаях клиентское materialized state не должно зависеть от случайного порядка сетевой доставки.
Correction и deletion — полноценные типы событий
Исправление лучше хранить как изменение версии, а не как ручную правку итоговой таблицы. Тогда state можно пересобрать из журнала либо из snapshot плюс хвост событий, что делает расследование воспроизводимым.
Reconnect проверяется вместе с пропусками
Нагрузочный тест без сетевых разрывов не покрывает главную эксплуатационную ветку. После reconnect система должна понимать, нужна ли дозагрузка, новый snapshot или повтор части журнала.
Почему порядок нельзя считать гарантированным
Даже если провайдер обещает последовательность, реальная цепочка включает сеть, брокер сообщений, ретраи и несколько потребителей. Поэтому система должна быть готова к повторной доставке и к тому, что более старое событие придёт позже нового. Sequence number помогает, но только если понятны правила его сброса, scope и поведение после reconnect.
Контрольные вопросы
- делать обработчик идемпотентным по event id или версии
- хранить watermark последнего применённого состояния
- иметь snapshot или replay для восстановления после разрыва
- отделять correction от обычного нового события
Самая опасная ошибка — «чинить» порядок только сортировкой по времени на клиенте. Клиент не знает всей истории и не должен заново собирать авторитетное состояние матча; это задача серверного state builder.
Позднее событие не всегда нужно отбрасывать
Правило «event time меньше последнего — discard» слишком грубое. Поздний пакет может не менять materialized state, а может содержать correction, без которой текущее состояние неверно. Решение должно опираться на семантику события и версию сущности. Для некоторых типов достаточно dedup по event ID, для других требуется пересчитать участок истории. Поэтому consumer полезно проектировать так, чтобы он различал duplicate, late-but-valid и superseding correction, а не складывал их в один класс out-of-order.
Практическая проверка feed-контракта — проиграть один и тот же набор событий в разных порядках и сравнить конечное состояние. Если результат зависит от случайного порядка доставки там, где бизнес-смысл этого не требует, проблема находится в модели версий или правилах применения corrections.
После разрыва соединения критична не сама скорость backfill, а возможность доказать, что gap закрыт. Для этого пригодится runbook безопасного восстановления replay backlog: диапазон должен иметь начало и конец, обработка — быть идемпотентной, а exit criteria — опираться на reconciled state, а не на пустую очередь. Пустая очередь может означать и потерянные сообщения, если consumer неверно перескочил checkpoint.
Sports feed редко ведёт себя как идеальная последовательность. Событие может прийти повторно, позже соседнего, с исправленным timestamp или с отменой предыдущего факта. Если pipeline умеет только добавлять новые записи, correction быстро превращается в ручную операцию. Надёжнее хранить исходное сообщение, нормализованную сущность и версию применения так, чтобы повторное проигрывание давало тот же итог. Тогда reconciliation становится проверяемой процедурой, а не набором специальных SQL-скриптов.
Коррекция должна быть обычным событием, а не исключением из модели
Отдельно стоит проверить clock assumptions. Provider timestamp, время приёма и время применения события — разные величины, и сортировка только по локальному receive time способна исказить порядок после сетевой задержки. Если контракт не гарантирует глобальную последовательность, pipeline должен опираться на версию, sequence или предметные правила, а timestamps использовать как диагностический контекст. Так поздняя доставка не превращается автоматически в «более новое» состояние.
Отдельная проверка нужна для смены версии протокола у поставщика. Если provider начинает присылать новый тип correction или меняет смысл sequence number, старый consumer не должен тихо принять пакет по прежним правилам. Безопаснее версионировать контракт, сохранять неизвестные события в карантин и выпускать поддержку после replay на записанном потоке. Так изменение схемы становится контролируемым релизом, а не источником расхождения состояния.
Связано по цепочке: Runbook для replay backlog после разрыва
