Поток CDC повторно доставляет одно изменение в аналитическое хранилище. Какой механизм должен обеспечить корректный итог?
Нужна идемпотентная обработка: повторное применение одного события не должно менять итог после первого успешного применения. Для этого событие должно иметь стабильный уникальный идентификатор или версию изменения, а приёмник — распознавать уже обработанные события и корректно выполнять вставку, обновление или удаление.
Одной защиты от дублей недостаточно, если события могут прийти в неправильном порядке. Поэтому обычно дополнительно проверяют версию или позицию изменения и не дают устаревшему событию перезаписать более новое состояние.
CDC, или захват изменений данных, появился как альтернатива регулярным полным выгрузкам из транзакционных систем. Полная загрузка плохо масштабируется, создаёт нагрузку на рабочую базу и задерживает появление изменений в аналитике.
Потоковая доставка уменьшает задержку, но при этом сталкивается с отказами сети, перезапусками потребителей и повторной отправкой сообщений. Поэтому архитектуры CDC часто проектируют с гарантией доставки at-least-once: событие может быть доставлено повторно, зато его потеря при сбое не считается нормальным результатом.
Пусть изменение строки сначала успешно применилось в хранилище, но подтверждение обработки потерялось. После перезапуска потребитель прочитает то же событие ещё раз. Если приёмник безусловно добавляет запись, появится дубликат; если безусловно перезаписывает данные, это может быть безопасно только при корректной идентификации записи и учёте порядка изменений.
Особенно опасен случай с несколькими событиями одной сущности. Более старое событие, пришедшее позже нового, способно откатить аналитическое состояние назад. Ошибки проявляются не только в количестве строк, но и в агрегатах, балансах, статусах и результатах последующих расчётов.
Основной механизм — идемпотентный приёмник. Он использует ключ события, например идентификатор изменения, комбинацию идентификатора сущности и версии либо монотонную позицию источника. При повторной доставке уже обработанное событие не должно порождать новый эффект.
Для построения текущего состояния обычно применяют операцию типа upsert в промежуточное или целевое хранилище. Новая запись вставляется, существующая обновляется, а удаление представляется явным событием, например маркером удаления или отдельным типом изменения.
Защиту от повторов можно реализовать несколькими способами:
Выбор зависит от модели данных и требований к задержке. Журнал обработанных идентификаторов даёт точную дедупликацию, но требует управления его размером и временем хранения. Проверка версии дешевле для текущего состояния, однако не подходит, если нужно сохранять каждое историческое изменение или если источник не предоставляет надёжную версию.
Важно различать идемпотентность и гарантию exactly-once. Идемпотентный приёмник делает повторную доставку безопасной, но не предотвращает саму повторную доставку. Сквозная гарантия exactly-once требует согласованной координации источника, брокера и приёмника и обычно сложнее, чем практическая схема с at-least-once и идемпотентным применением.
Порядок событий также должен быть частью контракта. Если источник даёт версию строки или последовательный номер изменения, приёмник сравнивает его с уже применённым значением. Если порядок не гарантирован и версии нет, надёжно восстановить последнее состояние в общем случае нельзя: остаётся применять дополнительную синхронизацию с источником или периодическую сверку.
Интернет-магазин передаёт в аналитическое хранилище изменения заказов. Событие о переходе заказа в статус «оплачен» было применено, но потребитель завершился до подтверждения брокеру; после перезапуска это событие пришло повторно, а затем с задержкой пришло более старое событие о статусе «создан».
Рассматривались три варианта. Простое добавление всех событий было легко реализовать, но искажало текущее состояние и агрегаты. Дедупликация только по содержимому события не гарантировала результат: два разных изменения могут иметь одинаковые поля. Периодическая полная перезагрузка устраняла часть ошибок, но создавала нагрузку и не обеспечивала свежесть данных.
Выбрали staging-слой с ключом события, версией изменения и типом операции. При повторной доставке одинаковый ключ не создаёт новую запись, а в целевое состояние применяется только событие с более высокой версией. В результате повторы стали безопасными, запаздывающие изменения не откатывали заказ, а staging-слой сохранил возможность расследовать ошибки доставки.
Компромисс решения — необходимость хранить метаданные CDC, контролировать уникальность ключей и отдельно обрабатывать события, для которых отсутствует версия. Также сохранили периодическую сверку с транзакционной системой, поскольку идемпотентность не защищает от повреждённых или потерянных исходных данных.
Нет. Первичный ключ сущности показывает, какую строку нужно изменить, но не различает последовательные изменения одной и той же строки. Для дедупликации нужен идентификатор события, а для контроля порядка — версия, временная метка с надёжной семантикой или позиция в журнале изменений.
Если хранить только последнее событие по первичному ключу, можно потерять историю, необходимую для аудита или пересчёта показателей. Поэтому модель текущего состояния и модель журнала изменений должны рассматриваться отдельно.
Если эти действия выполняются раздельно, между ними возникает окно отказа. При фиксации данных без отметки о событии последующий запуск обработает событие повторно; при фиксации отметки без изменения данных событие будет считаться обработанным, хотя его эффект потерян.
Атомарная фиксация устраняет эту несогласованность внутри приёмника. Если единая транзакция недоступна между всеми системами, применяют staging-слой, повторяемые операции и периодические сверки, принимая остаточный риск и контролируя его.
Нельзя делать это только по совпадению полезных полей или временной метки. Два независимых события могут законно содержать одинаковое состояние, а одинаковые данные не означают одинаковый эффект, особенно для операций, которые учитываются как факты или движения.
Дедупликация должна опираться на семантически стабильный ключ события и известный период его уникальности. Если ключи могут повторяться после очистки состояния, окно хранения метаданных должно соответствовать максимальной задержке доставки либо должна существовать возможность повторной сверки с источником.