Батчинг сообщений — это стратегия группировки нескольких сообщений в одну логическую или физическую отправку для уменьшения накладных расходов на сеть и повышения пропускной способности. В экосистеме STOMP каждое сообщение обычно представлено отдельным фреймом протокола, что означает наличие заголовков, служебных байтов и отдельного цикла обработки на брокере.
STOMP не накладывает встроенного механизма автоматического батчинга на уровне клиента, поэтому агрегация сообщений реализуется на стороне приложения. В STOMP.js это выражается в ручном управлении очередями сообщений и их последующей отправкой через единый канал публикации.
Передача каждого сообщения отдельно приводит к ряду накладных расходов:
Батчинг решает эти проблемы за счёт:
STOMP (Simple Text Oriented Messaging Protocol) работает поверх TCP и использует текстовые или бинарные фреймы. Каждый вызов отправки в STOMP.js обычно формирует отдельный frame:
SEND
destination:/queue/events
content-length:123
{...payload...}
Ключевые ограничения:
Существует несколько устойчивых подходов к группировке сообщений.
Сообщения накапливаются в буфере и отправляются через фиксированный интервал времени.
import { Client } fr om "@stomp/stompjs";
const client = new Client({
brokerURL: "ws://localhost:15674/ws",
});
const buffer = [];
let timer = null;
function flush() {
if (buffer.length === 0) return;
const batch = buffer.splice(0, buffer.length);
client.publish({
destination: "/queue/events.batch",
body: JSON.stringify(batch),
});
}
function sendMessage(msg) {
buffer.push(msg);
if (!timer) {
timer = setTimeout(() => {
flush();
timer = null;
}, 50);
}
}
client.activate();
Особенности:
Отправка происходит при достижении лимита сообщений.
const buffer = [];
const MAX_BATCH_SIZE = 100;
function flush() {
const batch = buffer.splice(0, buffer.length);
client.publish({
destination: "/queue/events.batch",
body: JSON.stringify(batch),
});
}
function sendMessage(msg) {
buffer.push(msg);
if (buffer.length >= MAX_BATCH_SIZE) {
flush();
}
}
Особенности:
На практике наиболее распространённая модель объединяет оба подхода.
const buffer = [];
const MAX_SIZE = 50;
const FLUSH_INTERVAL = 100;
let timer = null;
function flush() {
if (buffer.length === 0) return;
const batch = buffer.splice(0, buffer.length);
client.publish({
destination: "/queue/events.batch",
body: JSON.stringify(batch),
});
}
function scheduleFlush() {
if (timer) return;
timer = setTimeout(() => {
flush();
timer = null;
}, FLUSH_INTERVAL);
}
function sendMessage(msg) {
buffer.push(msg);
if (buffer.length >= MAX_SIZE) {
flush();
if (timer) {
clearTimeout(timer);
timer = null;
}
return;
}
scheduleFlush();
}
STOMP поддерживает транзакции, которые позволяют объединять несколько SEND операций в атомарную группу.
const tx = client.begin();
client.publish({
destination: "/queue/events",
body: JSON.stringify({ id: 1 }),
transaction: tx.id,
});
client.publish({
destination: "/queue/events",
body: JSON.stringify({ id: 2 }),
transaction: tx.id,
});
tx.commit();
Особенности транзакционного батчинга:
Так как STOMP не поддерживает нативные массивы сообщений, батч обычно кодируется в одном сообщении:
body: JSON.stringify([
{ type: "click", ts: 1710000000 },
{ type: "scroll", ts: 1710000001 }
])
Плюсы:
Минусы:
При высокой нагрузке используется бинарная сериализация:
const encoder = new TextEncoder();
function encodeBatch(batch) {
const json = JSON.stringify(batch);
return encoder.encode(json);
}
client.publish({
destination: "/queue/events.batch",
binaryBody: encodeBatch(buffer),
});
Особенности:
Размер батча критически влияет на стабильность системы.
Основные риски:
Практический подход — ограничение:
function getSize(batch) {
return new Blob([JSON.stringify(batch)]).size;
}
const MAX_BYTES = 128 * 1024;
function shouldFlush() {
return getSize(buffer) > MAX_BYTES;
}
При высокой скорости генерации событий буфер может расти неконтролируемо. В STOMP.js отсутствует встроенный механизм backpressure, поэтому он реализуется на уровне приложения.
Подходы:
const MAX_QUEUE = 1000;
function sendMessage(msg) {
if (buffer.length >= MAX_QUEUE) {
buffer.shift();
}
buffer.push(msg);
}
Батчинг изменяет семантику доставки:
Для строгого порядка используется:
При неудачной отправке одного батча весь payload считается недоставленным. STOMP не гарантирует автоматического retry на уровне клиента.
Стратегии:
function flushWithRetry(batch, attempt = 0) {
try {
client.publish({
destination: "/queue/events.batch",
body: JSON.stringify(batch),
});
} catch (e) {
if (attempt < 3) {
setTimeout(() => flushWithRetry(batch, attempt + 1), 100);
}
}
}
На стороне брокера и consumer логика должна учитывать, что:
Пример обработки (Node.js consumer):
const batch = JSON.parse(message.body);
for (const event of batch) {
processEvent(event);
}
Эффект батчинга зависит от профиля нагрузки:
Высокочастотные события:
Низкочастотные события:
Смешанные потоки: