Откат транзакции

Откат транзакции в STOMP.js является механизмом, позволяющим отменить все сообщения, отправленные в рамках транзакционного контекста, до момента их окончательного подтверждения брокером. При корректной реализации это обеспечивает атомарность набора операций: либо все сообщения фиксируются, либо ни одно из них не попадает в очередь. Такой подход критичен для сценариев, где частичная доставка данных недопустима — например, при синхронизации состояния, финансовых операциях или пакетной обработке событий.

Транзакции в STOMP основаны на идентификаторе транзакции, который связывает несколько кадров SEND в единый логический блок. Брокер, поддерживающий спецификацию STOMP (ActiveMQ, RabbitMQ с STOMP plugin, Apollo и др.), обрабатывает такие сообщения отложенно до момента получения команды завершения.

Ключевые особенности модели:

  • транзакция идентифицируется строковым transaction id;
  • все SEND-фреймы внутри транзакции маркируются заголовком transaction;
  • изменения становятся видимыми только после COMMIT;
  • до фиксации сообщения находятся в промежуточном состоянии.

STOMP.js реализует этот механизм через объект транзакции, предоставляя методы управления жизненным циклом.

Инициализация транзакции

Создание транзакции выполняется через клиентский объект соединения:

const tx = client.begin();

На уровне протокола отправляется frame:

BEGIN
transaction:tx-001

Идентификатор транзакции формируется клиентом или библиотекой и используется далее для привязки всех операций.

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

Отправка сообщений в рамках транзакции

Каждое сообщение, отправляемое в рамках транзакции, должно содержать ссылку на её идентификатор:

client.publish({
  destination: '/queue/orders',
  body: JSON.stringify({ orderId: 123 }),
  headers: {
    transaction: tx.id
  }
});

На уровне STOMP это превращается в:

SEND
destination:/queue/orders
transaction:tx-001

{"orderId":123}

Особенности поведения:

  • брокер не публикует сообщение до завершения транзакции;
  • сообщение временно хранится в буфере транзакции;
  • подтверждение доставки отсутствует до COMMIT.

При множественных SEND в рамках одной транзакции сохраняется порядок отправки, но окончательная атомарность зависит от реализации брокера.

Откат (abort) транзакции

Откат выполняется через метод:

tx.abort();

или явный вызов:

client.abort(tx.id);

На уровне протокола формируется frame:

ABORT
transaction:tx-001

После получения ABORT брокер обязан:

  • удалить все сообщения, связанные с транзакцией;
  • освободить временные ресурсы;
  • не публиковать ни одного из отправленных сообщений.

Ключевое свойство отката — полное исключение следов транзакции из очередей. Это не частичный rollback, а полная отмена блока операций.

Поведение при откате

При ABORT:

  • сообщения не попадают в destination;
  • потребители не получают никаких уведомлений;
  • подтверждения ACK не формируются;
  • состояние очереди остаётся неизменным.

Если часть сообщений уже была обработана брокером до получения ABORT (редкие случаи при нестандартных реализациях), возможны расхождения, что требует настройки уровня изоляции и проверки поддержки транзакций конкретным брокером.

Поведение брокера

Разные брокеры реализуют транзакции STOMP с вариациями:

  • ActiveMQ: полноценная поддержка XA-подобных транзакций, строгая атомарность;
  • RabbitMQ STOMP plugin: транзакции ограничены, возможны нюансы при высокой нагрузке;
  • Apollo: близко к спецификации, но зависит от конфигурации очередей.

Общие правила:

  • транзакция буферизуется на уровне session;
  • сообщения не видны consumer’ам до commit;
  • abort очищает буфер без публикации.

Некоторые брокеры используют журналирование транзакций, что влияет на производительность при частых откатах.

Типичные сценарии

Откат транзакции применяется в ситуациях, где требуется строгая целостность:

  • пакетная отправка событий, где ошибка в одном элементе делает недействительным весь пакет;
  • синхронизация состояния между сервисами;
  • обработка заказов, где проверка бизнес-правил выполняется до фиксации;
  • агрегированные операции записи в несколько очередей.

Пример сценария:

const tx = client.begin();

try {
  client.publish({
    destination: '/queue/payments',
    body: JSON.stringify({ amount: 100 }),
    headers: { transaction: tx.id }
  });

  client.publish({
    destination: '/queue/logs',
    body: 'payment initiated',
    headers: { transaction: tx.id }
  });

  tx.commit();
} catch (e) {
  tx.abort();
}

При любой ошибке оба сообщения будут отменены.

Ошибки и побочные эффекты

Откат транзакции не всегда является тривиальной операцией и может сопровождаться рядом проблем:

  • потеря состояния при разрыве соединения до ABORT;
  • зависшие транзакции при некорректной реализации брокера;
  • дублирование логики на стороне клиента при повторных попытках;
  • несогласованность при отсутствии поддержки транзакций в broker plugin.

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

Также важно учитывать, что rollback не отменяет уже доставленные сообщения, если брокер нарушает спецификацию или используется нестандартная конфигурация доставки.

Практика в STOMP.js

В STOMP.js управление откатом сводится к работе с объектом транзакции:

const tx = client.begin();

tx.abort();

или через клиент:

client.abort(tx.id);

Рекомендуется:

  • минимизировать время жизни транзакции;
  • избегать длительных асинхронных операций внутри транзакции;
  • обрабатывать ошибки до момента commit;
  • не смешивать транзакционные и нетранзакционные сообщения в одной логике отправки.

При высокой нагрузке транзакции следует использовать только там, где критична атомарность, так как каждая транзакция увеличивает нагрузку на брокер и снижает throughput очереди.

Поведение rollback тесно связано с ACK-моделью потребления: сообщения, отправленные в транзакции, не должны обрабатываться потребителями до её завершения, иначе нарушается гарантия целостности.