Содержание
В системах с transactional outbox и Kafka ключ партиционирования по order_id не гарантирует причинно-следственный порядок событий. Transactional outbox — это шаблон, при котором события сначала записываются в отдельную таблицу внутри той же транзакции, что и бизнес-данные, а затем отправляются в брокер. Три инцидента за неделю в одном магазине показали: записи OrderPaid и OrderPacked приходят в неправильной последовательности, витрина откатывает статусы, а после перезапуска часть событий теряется навсегда. Проблема возникает до брокера, в самом реле и у потребителя. При выборе подхода важно оценить, насколько критична строгая последовательность для витрины и уведомлений: если нарушение порядка приводит к финансовым или репутационным потерям, стоит рассмотреть дополнительные механизмы контроля или альтернативные шаблоны.
Почему вставка в outbox не сохраняет последовательность
Два обработчика — колбэк платёжного шлюса и сообщение от склада — пишут строки в таблицу outbox в отдельных транзакциях. Даже при одном aggregate_id bigserial присваивается в момент коммита, а не по бизнес-логике. Aggregate_id — это идентификатор агрегата, объединяющего связанные события, а bigserial — автоматически увеличивающийся счётчик в базе. Если платёж и отгрузка приходят почти одновременно, идентификатор OrderPacked может оказаться меньше, чем у OrderPaid. Relay читает пачку с ORDER BY id, поэтому уже на этом этапе последовательность нарушается. При принятии решения о внедрении важно проверить, как часто в проекте возникают одновременные события по одному заказу: высокая частота таких ситуаций повышает риск нарушения порядка ещё до отправки в брокер.
Дальше работает продюсер с настройками, которые допускают потерю порядка при повторных попытках. При асинхронной отправке и retries=10 первый запрос может упасть, а второй — успешно дойти раньше. В топике order-events с 12 партициями записи одного заказа окажутся в порядке поступления к брокеру, а не в порядке возникновения в домене. Это напрямую нарушает требование НФТ-07. Для оценки «брать или не брать» следует измерить, насколько часто сеть или брокер вызывают повторные отправки: если такие случаи регулярны, порядок событий становится непредсказуемым и может потребовать отказа от шаблона или добавления внешнего механизма упорядочивания.
Как потребитель добавляет свои нарушения
Витрина использует consumer group, где записи после poll() распределяются по пулу из восьми потоков. Consumer group — это группа потребителей, которые совместно читают топик и делят нагрузку. Каждый поток обрабатывает событие независимо и сообщает offset главному потоку. Offset — это позиция последнего прочитанного сообщения. При параллельной обработке OrderPacked может примениться после OrderPaid, даже если в топике они лежат правильно. Статус перезаписывается значением из последнего обработанного события, поэтому витрина показывает «Оплачен», хотя склад уже собрал заказ. При выборе решения важно понять, сколько потоков используется в витрине и есть ли проверка последовательности по идентификатору события: без такой проверки параллелизм становится источником ошибок.
После планового перезапуска потребитель начинает с последнего закоммиченного offset + 1. Если поток не успел сообщить offset перед остановкой, событие OrderPacked остаётся непрочитанным. Поскольку витрина не хранит outboxId и не проверяет дубли, пропущенное событие просто теряется. Share group для уведомлений клиентов усугубляет ситуацию: несколько экземпляров читают одну партицию без координации порядка. Для решения «брать или не брать» нужно оценить частоту перезапусков и наличие механизма восстановления пропущенных событий: если перезапуски происходят регулярно, а восстановление отсутствует, риск потери данных становится неприемлемым.
Как это проявляется в российских проектах
В отечественных командах часто используют PostgreSQL и Kafka 3.x-4.x с похожими настройками продюсера из старых шаблонов. RollingUpdate в Kubernetes позволяет старым и новым подам relay работать одновременно, усиливая гонки. Отключённая идемпотентность и пять in-flight запросов типичны для конфигураций, скопированных из проектов семилетней давности. В результате те же три инцидента повторяются в финтехе и e-commerce на российском стеке. При оценке подхода стоит проанализировать, применяется ли RollingUpdate и как часто обновляются поды: частые обновления увеличивают вероятность гонок и требуют либо доработки, либо отказа от текущей реализации.
Чтобы требование порядка стало проверяемым, его нужно переписать с указанием конкретных метрик: максимальная разница outboxId в одной партиции, время между коммитом и отправкой, а также обязательная проверка последовательности на стороне потребителя по outboxId. Без таких критериев ревью схемы остаётся формальным. При принятии решения о внедрении важно заложить эти метрики в процесс ревью, чтобы заранее выявить риски нарушения порядка.
Ссылка на разбор инцидентов: Kafka гарантирует порядок? Разбираем 3 инцидента с Outbox
Источник
Это краткий разбор материала Habr. Полная версия с примерами кода, схемами и деталями реализации — в оригинале: Порядок событий в Kafka Outbox ломается на трёх звеньях.
Курс по теме — Каталог курсов Школы Больших Данных


