Что такое паттерн аутбокс и зачем он нужен?
Что проверяют
Понимаете ли вы, что запись в базу и публикация в брокер это два разных ресурса, которые нельзя закоммитить одной транзакцией. Вопрос быстро уходит в цену решения: какие гарантии доставки вы в итоге получаете и что обязан делать потребитель.
Как отвечать
Начните с проблемы. Сервис меняет состояние и должен сообщить об этом другим. Сохранили в базу и упали до публикации, событие потеряно. Опубликовали, а транзакция откатилась, ушло событие о том, чего не произошло. Распределённая транзакция на двоих дорога и на практике почти не используется.
Решение: в той же транзакции, что и бизнес-изменение, пишем строку в таблицу исходящих сообщений. Оба изменения в одной базе, значит атомарность бесплатная. Отдельный процесс читает неотправленные строки, публикует их в брокер и помечает отправленными.
Детали, которых ждут дальше:
- разгребатель берёт пачку через
select ... for update skip locked, иначе два инстанса заберут одни и те же строки; - вместо опроса таблицы можно читать журнал репликации и снимать вставки через CDC;
- у сообщения есть ключ, по которому события одной сущности попадают в один раздел брокера и не обгоняют друг друга;
- отправленные строки чистятся или партиционируются по времени, иначе таблица растёт вечно.
Где спотыкаются
Говорят, что сообщение уходит в брокер внутри транзакции. В транзакции только строка в таблице, публикация всегда после коммита, и именно поэтому доставку не получается сделать однократной.
Обещают exactly-once. Аутбокс даёт at-least-once: между успешной публикацией и отметкой строки процесс может умереть, и сообщение уйдёт повторно. Значит потребитель обязан быть идемпотентным, обычно по идентификатору сообщения или по ключу бизнес-операции.
Пишут наивный разгребатель: выборка без блокировок и по одному сообщению в своей транзакции. При одном инстансе это тормозит, при нескольких начинает дублировать. Нужны пропуск заблокированных строк, батчи и частичный индекс по неотправленным.