Использовали ли транзакции при отправке сообщений в брокер
Что проверяют
Понимаете ли вы проблему двойной записи: изменить базу и отправить сообщение одной атомарной операцией нельзя. Следом почти всегда спрашивают про transactional outbox и про то, что будет, если сервис упадёт между коммитом и публикацией.
Как отвечать
Начните с проблемы. Если публиковать внутри транзакции базы, сообщение уйдёт, а транзакция может откатиться, и потребители узнают о событии, которого не было. Если публиковать после коммита, падение процесса между двумя шагами теряет событие. Распределённые транзакции между базой и брокером на практике почти не применяют: это сложно и медленно.
Стандартное решение:
- transactional outbox: событие пишется в таблицу
outboxв той же транзакции, что и бизнес-данные. Отдельный воркер читает таблицу и публикует, либо изменения забирает CDC, например Debezium; - выборка без дублей: если воркеров несколько, строки берут через
SELECT ... FOR UPDATE SKIP LOCKEDи помечают отправленными после подтверждения брокера; - идемпотентный потребитель: outbox даёт доставку «хотя бы один раз», поэтому дубли возможны. Потребитель хранит идентификаторы обработанных сообщений или делает саму операцию идемпотентной.
Затем про транзакции самого брокера. В Kafka есть транзакционный продюсер с transactional.id: он атомарно пишет в несколько партиций и коммитит оффсеты в цепочке «прочитал, обработал, записал», а потребители с isolation.level=read_committed не видят незакоммиченное. В Go транзакции поддерживают, например, franz-go и confluent-kafka-go, но не все клиенты, это стоит проверить. В RabbitMQ вместо медленных AMQP-транзакций используют publisher confirms: Confirm на канале в amqp091-go и ожидание подтверждения.
Если использовали, расскажите свой случай, если нет, покажите, как построили бы outbox.
Где спотыкаются
Считают, что транзакции Kafka охватывают и базу. Они атомарны только внутри Kafka, до записи в Postgres не дотягиваются.
Обещают exactly-once от начала до конца и не делают потребителя идемпотентным.
Публикуют прямо из хендлера после tx.Commit() без ретраев и outbox и называют это надёжной отправкой.