Time-to-live сообщений

Механизм TTL на уровне STOMP-протокола

В STOMP-протоколе отсутствует единое строго обязательное поле для управления временем жизни сообщения, однако большинство брокеров реализуют поддержку TTL через заголовки сообщения. В контексте STOMP.js TTL реализуется через передачу специальных headers при отправке сообщения, которые затем интерпретируются брокером (например, ActiveMQ, RabbitMQ, Apollo, Artemis).

Основная идея TTL заключается в ограничении времени существования сообщения в очереди. Если сообщение не было доставлено потребителю за заданный интервал, оно либо удаляется, либо перенаправляется в dead-letter queue (DLQ), в зависимости от конфигурации брокера.

Ключевой момент: STOMP.js не реализует TTL самостоятельно — он лишь передаёт метаданные, а вся логика исполнения находится на стороне брокера.


Основные способы задания TTL

В зависимости от брокера используются разные заголовки:

  • expiration — абсолютное время истечения сообщения (в миллисекундах epoch time)
  • time-to-live — относительное время жизни сообщения
  • message-ttl — часто используется в RabbitMQ через headers или queue arguments

На практике наиболее универсальным является заголовок expiration.


Установка TTL при отправке сообщения в STOMP.js

При использовании STOMP.js TTL задаётся через объект headers в методе publish или send.

client.publish({
  destination: "/queue/orders",
  body: JSON.stringify({ orderId: 123 }),
  headers: {
    expiration: (Date.now() + 10000).toString()
  }
});

В этом примере сообщение будет действительно только 10 секунд. После этого брокер может удалить его, если оно не было доставлено.


Относительный TTL и его вычисление

STOMP-протокол не стандартизирует относительный TTL напрямую, поэтому он эмулируется вычислением абсолютного времени:

const ttl = 30_000; // 30 секунд

client.publish({
  destination: "/queue/notifications",
  body: JSON.stringify({ text: "Hello" }),
  headers: {
    expiration: String(Date.now() + ttl)
  }
});

Подобный подход используется во всех клиентских реализациях STOMP.js независимо от версии.


TTL на уровне брокера и взаимодействие со STOMP.js

RabbitMQ

В RabbitMQ TTL может задаваться тремя способами:

  1. TTL очереди (queue-level)
  2. TTL сообщения (message-level)
  3. TTL через policy

STOMP.js в этом случае передаёт только headers, а брокер интерпретирует их:

client.publish({
  destination: "/queue/task",
  body: "data",
  headers: {
    "expiration": String(Date.now() + 60000)
  }
});

На стороне RabbitMQ это соответствует per-message TTL.


ActiveMQ / Artemis

В ActiveMQ поддерживаются:

  • time-to-live
  • JMSExpiration (внутренний аналог expiration)

Пример:

client.publish({
  destination: "/queue/jobs",
  body: "process",
  headers: {
    "time-to-live": "45000"
  }
});

Однако фактическая поддержка зависит от STOMP adapter слоя брокера.


Поведение сообщений с истекшим TTL

Когда TTL истекает, поведение зависит от конфигурации брокера:

  1. Удаление без следа Сообщение просто удаляется из очереди без уведомления клиента.

  2. Перемещение в Dead Letter Queue Используется в системах с критичной доставкой сообщений.

  3. Переотправка или логирование Некоторые брокеры могут логировать истечение TTL.

Важно учитывать, что STOMP.js не получает обратного уведомления о том, что сообщение истекло.


TTL и подтверждение доставки (ACK)

TTL тесно связан с механизмом подтверждений:

  • Если сообщение не было подтверждено (ACK) до истечения TTL, оно считается просроченным
  • При client.ack() TTL уже не играет роли
  • При client.nack() сообщение может быть возвращено в очередь, если TTL не истёк

Пример подписки:

client.subscribe("/queue/tasks", (message) => {
  const data = JSON.parse(message.body);

  processTask(data);

  message.ack();
});

Если ack() не вызван и TTL истекает, сообщение может быть удалено брокером.


Сценарии использования TTL

Очереди уведомлений

TTL используется для предотвращения доставки устаревших уведомлений:

client.publish({
  destination: "/queue/alerts",
  body: "system update",
  headers: {
    expiration: String(Date.now() + 15000)
  }
});

Очереди задач

В системах фоновой обработки TTL предотвращает выполнение устаревших задач:

  • задачи с коротким жизненным циклом
  • задачи, зависящие от времени (например, кеширование)

Реалтайм-сообщения

TTL ограничивает доставку сообщений, если клиент долго был офлайн:

  • чат-сообщения
  • push-уведомления
  • игровые события

Ограничения TTL в STOMP.js

  1. STOMP.js не гарантирует соблюдение TTL
  2. TTL зависит от брокера
  3. Нет стандартизированного поведения между реализациями
  4. Нет обратного события об истечении TTL
  5. Возможны расхождения между queue TTL и message TTL

Частые ошибки при работе с TTL

Использование Date.now() без приведения к строке

Некоторые брокеры требуют строковый формат:

headers: {
  expiration: Date.now().toString()
}

Путаница между TTL очереди и сообщения

TTL очереди задаётся при создании queue и не изменяется через STOMP.js:

  • queue TTL — серверная конфигурация
  • message TTL — клиентский header

Установка слишком малого TTL

При высокой нагрузке сообщение может истечь до обработки:

  • задержки сети
  • блокировка consumer
  • медленный broker routing

TTL и масштабируемые системы

В распределённых архитектурах TTL используется как механизм защиты от:

  • перегрузки очередей
  • накопления устаревших сообщений
  • повторной обработки старых событий

STOMP.js в таких системах выступает лишь транспортным слоем, а TTL становится частью общей политики маршрутизации сообщений.


Влияние TTL на производительность брокера

Наличие TTL влияет на:

  • частоту очистки очередей
  • нагрузку на garbage collector брокера
  • объем метаданных сообщений
  • использование DLQ

В некоторых брокерах массовое использование TTL может приводить к дополнительным накладным расходам на проверку истечения срока жизни сообщений.


TTL и транзакционные сообщения

При использовании транзакций:

  • TTL начинает отсчитываться с момента отправки
  • транзакционная задержка не продлевает TTL
  • откат транзакции может привести к пересозданию сообщения с новым TTL
client.begin();
client.publish({
  destination: "/queue/tx",
  body: "data",
  headers: {
    expiration: String(Date.now() + 20000)
  }
});
client.commit();

Совместимость и различия реализаций

Разные брокеры интерпретируют TTL по-разному:

  • RabbitMQ: строго per-message TTL, учитывается в миллисекундах
  • ActiveMQ: поддерживает несколько моделей TTL
  • Apollo: зависит от конфигурации destination
  • Artemis: расширенная поддержка JMS TTL семантики

STOMP.js не нормализует эти различия, поэтому поведение всегда зависит от серверной стороны.


TTL и потоковая доставка сообщений

В системах с высокой скоростью сообщений TTL может использоваться как фильтр потока:

  • сообщения устаревают быстрее, чем потребляются
  • брокер автоматически очищает backlog
  • consumer получает только актуальные события

Это особенно важно в:

  • системах мониторинга
  • финансовых котировках
  • телеметрии устройств