Очередь обмена: сообщение должно иметь биографию

Очередь нужна не для того, чтобы «отправлять в фоне». Её главная работа — сохранить правду о сообщении между двумя системами, которые могут падать независимо.

Рассмотрим выгрузку цен из 1С на сайт. При прямом вызове пользовательская запись номенклатуры зависит от доступности сайта. При очереди 1С фиксирует намерение «цена SKU-17 стала 840 рублей», а отдельный процесс занимается доставкой. Но одна таблица с флагом Отправлено быстро превращается в тупик: непонятно, кто взял сообщение, сколько было попыток и можно ли запускать его ещё раз.

Шесть состояний вместо одного флага

Новое ждёт обработки. В работе имеет владельца и срок аренды. Доставлено содержит подтверждение внешней стороны. Отложено знает время следующей попытки. Карантин требует решения человека. Отменено сохраняется для аудита, но больше не отправляется.

Аренда важнее поля «обрабатывается». Если фоновое задание умерло, бессрочный флаг навсегда запирает запись. У аренды есть locked_until; после этого времени другой исполнитель может безопасно подобрать сообщение.

Отбор пачки без гонки

  1. Выбрать ограниченную пачку новых или просроченных сообщений.
  2. В короткой транзакции пометить их владельцем и сроком аренды.
  3. Завершить транзакцию до сетевого вызова.
  4. Отправлять каждое сообщение с его постоянным идентификатором.
  5. Отдельно фиксировать подтверждение или новую попытку.

Держать транзакцию во время HTTP-вызова нельзя: медленный сайт удерживает блокировки в рабочей базе. Между шагами 3 и 5 процесс может упасть, поэтому получатель всё равно обязан быть идемпотентным.

message_id: price-SKU17-000184
aggregate:  SKU-17
sequence:   184
state:      delayed
attempt:    3
next_try:   2026-08-23T10:20:00+03:00
last_error: HTTP 503
locked_by:  null
locked_until: null

Порядок нужен не всем сообщениям

Глобальная последовательность убивает параллельность. Для цен достаточно порядка внутри одного товара: сообщение 184 для SKU-17 не должно быть применено после 185, но цена другого товара может ехать одновременно. В сообщение добавляют версию агрегата, а получатель отвергает устаревшую.

Иногда лучше схлопывать очередь. Если цена менялась десять раз, а сайт был недоступен, ему нужна последняя цена, а не десять промежуточных. Для финансовых проводок такое объединение недопустимо: каждое событие имеет самостоятельный смысл. Решение определяется предметной областью, а не возможностями брокера.

Карантин — это рабочее место, не кладбище

После ограниченного числа технических попыток сообщение уходит в карантин. Оператор видит обезличенное содержание, историю ответов, ссылку на исходный объект и допустимые действия: повторить без изменения, исправить справочное соответствие, сформировать новое сообщение или отменить. Кнопка «повторить всё» без фильтра обычно создаёт вторую аварию.

Метрики очереди отвечают на четыре вопроса: сколько новых сообщений, возраст самого старого, доля ошибок и скорость успешной доставки. Одна только длина вводит в заблуждение: тысяча свежих сообщений при производительности две тысячи в минуту безопаснее пяти сообщений, застрявших со вчерашнего дня.

Учебный эксперимент: запустите два обработчика одновременно, остановите один после получения аренды и верните через пять минут. Убедитесь, что второму достались только свободные записи, а просроченная аренда была восстановлена без дубля.

Хорошая очередь не обещает невозможного «никогда не будет повторов». Она делает повторы безопасными, хранит историю решений и позволяет доказать судьбу конкретного сообщения.

Проверьте себя
1. Зачем сообщению срок аренды locked_until?
AЧтобы зашифровать тело
BЧтобы запись можно было подобрать после падения обработчика
CЧтобы сохранить пароль API
DЧтобы сортировать товары по цене
2. Почему сетевой вызов не выполняют внутри долгой транзакции 1С?
AHTTP запрещён в транзакциях синтаксисом
BМедленный ответ удерживает блокировки рабочей базы
CТранзакция меняет JSON
DСайт всегда отвечает 409
3. Какая метрика лучше обнаруживает старое зависшее сообщение?
AРазмер текста ошибки
BВозраст самого старого элемента очереди
CКоличество колонок регистра
DСредняя цена товара