Transactional Outbox для SMS: как не потерять уведомление после изменения данных

1 мин
Время чтения

Приложению часто нужно выполнить два действия: изменить бизнес-данные и отправить SMS. Например, подтвердить оплату в базе и создать уведомление клиенту. Если сначала сохранить оплату, а затем вызвать внешний API, процесс может упасть между шагами — платёж проведён, но сообщение потеряно. Если сначала отправить SMS, а затем не сохранить изменение, клиент получит уведомление о событии, которого система не зафиксировала.

Transactional Outbox решает проблему двойной записи. Бизнес-изменение и запись о будущем уведомлении сохраняются в одной транзакции локальной базы. Отдельный worker читает outbox и передаёт задачи в очередь SMS или сразу в сервис отправки.

Почему обычный вызов после commit ненадёжен

Код может выглядеть последовательно: обновить заказ, выполнить commit, вызвать SMS API. Но между commit и сетевым запросом остаётся окно сбоя. Повтор всей операции тоже опасен: обновление уже произошло, а приложение не всегда понимает, была ли отправка принята.

Распределённая транзакция с внешним SMS-провайдером обычно недоступна и не нужна. Outbox переносит атомарность туда, где приложение её контролирует: в собственную базу.

Состав записи outbox

Минимальные поля:

  • уникальный event_id;
  • тип события и версия схемы;
  • идентификатор объекта, например заказа;
  • время создания;
  • полезная нагрузка или ссылка на необходимые данные;
  • статус обработки;
  • число попыток и время следующей;
  • idempotency key будущей отправки;
  • correlation ID для диагностики.

Не помещайте в outbox API-ключи и лишние персональные данные. Если текст можно сформировать по защищённому шаблону позже, храните ссылку на версию шаблона и минимальный набор переменных.

Атомарная запись

При изменении заказа приложение внутри одной транзакции обновляет таблицу заказа и вставляет строку outbox. Если любой шаг завершается ошибкой, откатываются оба. Если commit успешен, задача гарантированно существует в локальной базе, даже если worker временно не работает.

Именно это свойство отличает outbox от обычного вызова очереди после транзакции. Подход описан в Azure Architecture Center: состояние и событие сохраняются атомарно, а отдельный процесс публикует необработанные записи.

Работа publisher

Worker выбирает необработанные строки небольшими пакетами, блокирует их от параллельной обработки, публикует и отмечает результат. Нужно заранее выбрать семантику блокировки: SELECT ... FOR UPDATE SKIP LOCKED, lease с временем истечения или другой механизм, доступный базе.

Отметка «обработано» происходит после подтверждения следующего звена. Если worker упал после публикации, но до отметки, запись будет выбрана повторно. Поэтому downstream-потребитель и SMS-отправка должны быть идемпотентными. Outbox обычно гарантирует at-least-once, а не магическое exactly-once.

Порядок событий

Для одного объекта порядок может быть важен: «заказ создан» должен обрабатываться раньше «заказ отменён». Глобальный порядок всей таблицы редко нужен и мешает масштабированию. Обычно сохраняют последовательность внутри aggregate ID или partition key.

Если позднее событие уже обработано, устаревшее уведомление нужно остановить. Перед отправкой worker может проверить текущую версию объекта или использовать номер версии события. Особенно это важно для статусов, которые быстро меняются.

Формирование текста

Есть два подхода. Первый — записать полностью сформированное сообщение в outbox. Он сохраняет точное содержание на момент события, но увеличивает объём персональных данных и усложняет исправление шаблона.

Второй — сохранить тип события, версию шаблона и данные подстановки. Worker формирует текст позднее. Тогда версия шаблона должна быть неизменяемой, иначе повтор одной записи создаст другой текст. Компромисс выбирают по аудиту и требованиям сценария.

Ошибки и карантин

Временные ошибки получают ограниченные повторы с задержкой. Постоянные — например неизвестная версия события или отсутствующая обязательная переменная — не должны бесконечно блокировать таблицу. После лимита запись переводят в отдельное состояние для разбора.

Важно разделять успех публикации события и успех доставки SMS. Outbox может считаться обработанным после того, как задача надёжно попала в сервис отправки. Дальнейший жизненный цикл отслеживается по message ID и DLR.

Очистка

Обработанные строки нельзя удалять сразу: они нужны для расследования и защиты от повторной публикации. Задайте retention, архив или компактную таблицу обработанных event ID. Очистка идёт пакетами, чтобы не создавать долгие блокировки.

Мониторьте возраст самой старой необработанной записи, размер backlog, скорость обработки, число повторов и записи в ошибочном состоянии. Растущий возраст outbox — ранний сигнал, что уведомления уже задерживаются, даже если SMS API не показывает ошибок.

Когда паттерн не нужен

Если SMS создаётся вручную вне бизнес-транзакции и допустима явная ошибка пользователю, обычного надёжного запроса может быть достаточно. Outbox полезен там, где событие обязано породить уведомление и нельзя атомарно записать данные в две независимые системы.

Не стоит внедрять сложный фреймворк только ради одного редкого сценария. Но и заменять outbox периодическим поиском «заказов без SMS» рискованно: такой reconciliation полезен как контроль, а не как основной механизм.

Чек-лист

  • бизнес-изменение и outbox вставляются в одной транзакции;
  • event ID уникален, схема версионируется;
  • несколько workers не забирают запись одновременно;
  • повтор публикации безопасен;
  • порядок определён только там, где нужен;
  • устаревшее событие не отправляет вводящее в заблуждение SMS;
  • ошибки имеют лимит попыток и отдельное состояние;
  • метрики показывают backlog и возраст записей;
  • очистка не удаляет данные раньше окна повторов и расследования.

Итог

Transactional Outbox закрывает разрыв между изменением данных и созданием SMS-задачи. Он не обещает ровно одну доставку сам по себе, зато гарантирует, что зафиксированное бизнес-событие не исчезнет между commit и внешним вызовом. В сочетании с идемпотентностью, очередью и мониторингом это основа надёжных уведомлений.

Обновлено 25 сентября, 2026

Общая учётная запись администратора удобна до...

7 октября, 2026

Интеграция может месяцами работать, а затем...

Подходит каждому
SMS-рассылка для любой сферы бизнеса
Узнайте как внедрить SMS-рассылку в ваш бизнес вместе с quicktel
Оставьте номер и вам перезвонит менеджер.