Как правильно организовать outbox pattern для гарантии ровно одного чтения сообщений
Что проверяют
Главная ловушка в формулировке: outbox сам по себе не даёт «ровно один раз». Интервьюер ждёт, что вы это скажете и объясните, как получить эффект однократной обработки. Следом спрашивают про порядок сообщений и несколько экземпляров relay.
Как отвечать
Outbox решает проблему двойной записи: изменение в базе и публикацию в брокер нельзя атомарно провести в двух системах. Поэтому событие пишется в таблицу outbox той же транзакцией, что и бизнес-данные, а отправкой занимается отдельный процесс.
- запись: в Go это один
txизpgxилиdatabase/sql,defer tx.Rollback(ctx)иtx.Commit(ctx)в конце. В таблицеid(UUID, он же ключ идемпотентности),aggregate_id,payload,created_at,sent_at; - relay через polling:
SELECT ... WHERE sent_at IS NULL ORDER BY id LIMIT 100 FOR UPDATE SKIP LOCKEDв транзакции, отправка, отметкаsent_at, коммит.SKIP LOCKEDне даёт двум воркерам взять одни строки; - relay через CDC: Debezium читает WAL и публикует записи outbox без опроса таблицы;
- почему дубли неизбежны: если процесс упал после подтверждения брокера, но до коммита отметки, сообщение уйдёт повторно. Это at-least-once;
- идемпотентный консьюмер: у получателя таблица
inboxс уникальнымmessage_id. В одной транзакцииINSERT ... ON CONFLICT DO NOTHINGи бизнес-изменение; вставилось ноль строк, значит, сообщение уже обработано. Офсет коммитится после транзакции.
Итог: at-least-once доставка плюс идемпотентная обработка дают эффект exactly-once. Порядок сохраняется, если ключ сообщения равен aggregate_id, а события одного агрегата отправляются последовательно.
Где спотыкаются
Утверждают, что outbox гарантирует ровно одну доставку. Это сразу показывает, что падение между отправкой и отметкой не продумано.
Надеются на идемпотентного продюсера Kafka (enable.idempotence). Он убирает дубли повторов внутри одной сессии продюсера, но не повторную отправку после рестарта relay.
Не думают о росте таблицы. Отправленные строки нужно чистить пачками или партиционировать outbox по времени, иначе опрос замедляется с каждой неделей.