Сколько RAM нужно для NATS
«NATS же лёгкий, ему хватит 512 МБ» — правда только для голого core-брокера без персистентности. Стоит включить JetStream, поднять пару стримов с приличным ретеншном и добавить кластер на три ноды — и память начинает уходить совсем не туда, где её ждали. Разберём, из чего на самом деле складывается расход RAM у NATS, почему JetStream и Raft-репликация — это две разные статьи бюджета, и как посчитать, сколько сервера брать под свою нагрузку, а не гадать по чужим тарифам.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Короткий ответ: сколько RAM нужно для NATS
Точных лабораторных цифр в духе «замерили — получили N мегабайт» здесь дать честно нельзя: расход сильно зависит от числа подключений, subject-топологии, размера сообщений и настроек ретеншна в JetStream. Но есть рабочие ориентиры, по которым можно стартовать:
| RAM сервера | Что реально тянет |
|---|---|
| 512 МБ | голый core NATS без JetStream: pub/sub, request/reply, до пары тысяч подключений |
| 1 ГБ | core в проде + JetStream с небольшими file-стримами (десятки-сотни МБ данных) |
| 2 ГБ | JetStream в бою: несколько стримов, тысячи подключений, консьюмеры с ack-трекингом |
| 4 ГБ | кластер из 3 нод с репликацией R=3, десятки стримов, активный трафик |
| 8+ ГБ | memory-стримы с большим max_bytes, высокая кардинальность subject'ов, крупный кластер |
Главное практическое правило: core NATS почти ничего не весит, весит JetStream и то, что вы в нём храните. Если сервис использует NATS только как шину сообщений без персистентности — 512 МБ - 1 ГБ хватает с большим запасом. Как только появляется nats stream add с реальным ретеншном — считать нужно уже не сервер, а объём данных, который вы просите NATS держать.
Из чего складывается базовый расход памяти
NATS-сервер — один статический Go-бинарник без внешних зависимостей: не нужен Zookeeper как у Kafka и не нужен Erlang VM как у RabbitMQ, и это первая причина, почему базовый footprint у него ощутимо ниже. Но внутри всё равно есть три источника расхода, которые растут с нагрузкой, а не остаются константой:
- Sublist — дерево интереса. Каждая подписка (
SUB) добавляется в trie-структуру, по которой роутер быстро находит подписчиков для входящего сообщения. Чем больше уникальных subject-паттернов и чем активнее используются wildcard'ы (*,>), тем тяжелее дерево. Тысяча плоских подписок почти не заметна; тысяча подписок с широкими wildcard-паттернами — уже другая история. - Буферы на подключение. У каждого клиентского соединения свои read/write-буферы и очередь исходящих сообщений. Ключевой параметр —
max_pending(по умолчанию 64 МБ на подключение): потолок непрочитанных клиентом данных, после которого сервер решает, что клиент не успевает, и рвёт соединение как Slow Consumer. Лимит не резервирует память заранее, но определяет, сколько сервер согласен накопить на одно медленное соединение, прежде чем защититься. max_payload. По умолчанию 1 МБ — верхняя граница размера одного сообщения. Если сервисы шлют мегабайтные пейлоады пачками, а не редкие килобайтные события, буферы на конкурентных публикациях растут кратно быстрее.
Ещё один источник, о котором забывают: NATS написан на Go, и по умолчанию действует стандартная политика сборщика мусора GOGC=100 — куча может вырасти вдвое, прежде чем GC её подрежет. На сервере, где памяти впритык, это ощущается как «утечка», хотя формально это штатное поведение рантайма.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверJetStream: где на самом деле живёт нагрузка
JetStream — это слой персистентности поверх core NATS, и именно он превращает «лёгкий брокер» в сервис с реальными требованиями к ресурсам. Тип хранилища стрима задаётся при создании и меняет картину принципиально:
nats stream add ORDERS \
--subjects "orders.>" \
--storage file \
--retention limits \
--max-bytes=10GB \
--max-msgs=-1 \
--max-age=7d \
--replicas=1
storage: memory. Все сообщения стрима лежат в оперативной памяти процесса до конфигурированногоmax_bytes. Это самый быстрый вариант и самый прямолинейный по расходу: настроили лимит в 2 ГБ на стрим — заложите эти 2 ГБ в RAM сервера буквально.storage: file. Сообщения пишутся на диск блоками, RAM-профиль процесса заметно легче — в памяти держатся индексы, метаданные и окно дедупликации, а не сами сообщения. Но здесь вступает ОС: чтение с диска активно использует page cache, и если стрим горячий, ядро охотно отъедает под кэш всю свободную память, какую найдёт. Это не течь NATS, а обычное поведение Linux, но на сервере с 1-2 ГБ разница между «есть запас» и «нет» ощущается быстро.- Консьюмеры. Durable-консьюмер с
ack policy: explicitдержит в памяти состояние подтверждений — ack floor, pending-сообщения, окно redelivery. На стриме с растущей очередью непрочитанного (медленный или упавший подписчик) это состояние растёт вместе с очередью, и именно здесь чаще рождается практический OOM, а не в самом хранилище стрима.
Отдельно — duplicate_window (дедупликация по Nats-Msg-Id): чем шире окно, тем дольше сервер держит отпечатки уже виденных сообщений. Значение по умолчанию 2 минуты обычно безобидно, но расширенное до часов ради идемпотентности при ретраях — заметная добавка к RSS, о которой легко забыть.
Кластер и Raft: репликация — отдельная статья расходов
Одна нода JetStream — это просто процесс со своим хранилищем. Кластер из нескольких нод с репликацией стримов (--replicas=3) добавляет Raft, и вот тут расход RAM растёт нелинейно относительно числа нод.
У каждого реплицируемого объекта — свой Raft-group: есть meta-группа кластера (распределяет стримы по нодам), отдельная Raft-группа на каждый стрим с R>1, и своя группа на каждый durable-консьюмер, если репликация включена и для консьюмеров. На практике: не «умножили RAM на 3 ноды», а «умножили на число Raft-групп». Кластер с одним крупным стримом на трёх нодах — недорого. Кластер с полусотней стримов по три реплики — это полсотни независимых Raft-логов, и на ноде в 2 ГБ они начинают заметно конкурировать за память.
Практический вывод для планирования: если архитектура тяготеет к multi-tenant с десятками мелких стримов (например, отдельный стрим на каждого клиента или каждый микросервис), закладывайте RAM с запасом на количество Raft-групп, а не на суммарный объём данных — при таком паттерне число групп обычно и есть узкое место, а не байты полезной нагрузки.
Как посчитать RAM под свою нагрузку
Формула для прикидки снизу вверх:
RAM = baseline_core (150-300 МБ)
+ memory_streams_max_bytes (сумма max_bytes всех memory-стримов)
+ file_streams_overhead (индексы + консьюмеры, ~5-10% от объёма данных на диске)
+ connections_margin (запас на буферы при пиковой конкурентности)
+ GC_headroom (20-30% сверху на паузы сборщика мусора)
baseline_core — сам процесс без данных: рантайм Go, sublist, служебные подписки системного account. Для memory-стримов считаем буквально — что задали в max_bytes, то и займётся. Для file-стримов важен не размер данных на диске (он может быть терабайтным), а накладные расходы поверх него: индексы, состояние consumer'ов, окно дедупликации — обычно единицы процентов от объёма, но растёт с числом активных консьюмеров.
Проверить фактическое потребление на живом сервере — не гадать, а спросить сам процесс:
curl -s localhost:8222/varz | jq '.mem, .cpu, .connections'
curl -s localhost:8222/jsz?streams=true | jq '.memory, .storage, .streams'
curl -s localhost:8222/connz?subscriptions=true | jq '.num_connections'
/varz отдаёт RSS процесса и число подключений, /jsz — сколько именно занимает JetStream по памяти и по диску отдельно, /connz — детализацию по соединениям, если нужно найти прожорливого клиента. Утилита nats-top (часть nats CLI) даёт то же живым потоком, как обычный top, но по объектам NATS — удобно смотреть под нагрузкой, не дёргая curl вручную.
Мониторинг и признаки нехватки памяти
Три сценария, которые встречаются на практике, и они лечатся по-разному:
- Slow Consumer. Клиент не успевает вычитывать сообщения, накопленный pending превышает
max_pending, сервер обрывает соединение и пишет в логSlow Consumer Detected. Это защитный механизм, а не крах: NATS жертвует одним медленным клиентом, чтобы не раздуть память под его очередь до бесконечности. Если это происходит регулярно — проблема на стороне потребителя, а не в объёме RAM сервера. - JetStream упирается в
max_bytesaccount'а. При превышении лимита (JS account resource limits exceeded) новые публикации в стрим отклоняются. Память сервера может быть в порядке — это отдельный, явно заданный потолок, поднимать который стоит осознанно черезnats account info, а не как реакцию на нехватку RAM. - OOM-killer ядра. Если потребление процесса вместе с page cache под file-стримы упёрлось в физический предел, ядро убьёт процесс без предупреждения в логах самого NATS — только
dmesg -T | grep -i "out of memory"покажет причину. ПомогаетGOMEMLIMIT, выставленный чуть ниже физической памяти сервера, чтобы Go-рантайм сам ужимал кучу, не доводя дело до ядра:
mkdir -p /etc/systemd/system/nats-server.service.d
cat >/etc/systemd/system/nats-server.service.d/mem.conf <<'EOF'
[Service]
Environment=GOMEMLIMIT=1600MiB
EOF
systemctl daemon-reload && systemctl restart nats-server
Для регулярного мониторинга разумнее не собирать метрики вручную, а поднять экспортер prometheus-nats-exporter, который снимает те же /varz, /jsz, /connz и отдаёт их в формате Prometheus — тогда графики по памяти, числу подключений и Slow Consumer накапливаются автоматически. Если связка Prometheus и Grafana ещё не настроена — есть отдельный разбор Prometheus и Grafana: связка и настройка.
Какой сервер взять под NATS в MAATRIX
Отталкивайтесь не от абстрактного «сколько микросервисов», а от того, включаете JetStream или нет и какой storage выбираете для стримов:
| Задача | Конфигурация | Что помещается |
|---|---|---|
| Только core, без персистентности | 1 vCPU / 512 МБ / 10 ГБ NVMe | pub/sub и request/reply между сервисами, тестовый стенд |
| JetStream, file-стримы, соло-нода | 2 vCPU / 2 ГБ / 40 ГБ NVMe | несколько стримов с ретеншном по дням, боевой минимум |
| JetStream, кластер из 3 нод | 3 × 2 vCPU / 4 ГБ / 60 ГБ NVMe | репликация R=3, отказоустойчивость, десятки стримов |
| Memory-стримы под низкую задержку | 4 vCPU / 8 ГБ / 60 ГБ NVMe | стримы целиком в RAM, минимальная задержка публикации |
| Высокая кардинальность subject'ов | 4 vCPU / 8+ ГБ / 80 ГБ NVMe | тысячи уникальных wildcard-подписок, тяжёлый sublist |
NATS — тот случай, где топология сети до брокера решает не меньше, чем память сервера: если сервисы разнесены по локациям, задержка на publish/ack напрямую бьёт по throughput. Для кластера, обслуживающего сервисы в Европе, обычно берут UK, Лондон — низкая задержка до точек обмена и чистые IP для интеграций; US — когда часть сервисов сама живёт в американских дата-центрах, RU — если по 152-ФЗ данные обязаны оставаться в России. Смежный разбор — сколько ресурсов нужно VPS для разработчика и CI/CD, общий подход к запасу по памяти — в статье сколько оперативной памяти закладывать с запасом.
Оплата — картой российского банка, по СБП, криптовалютой или токеном MAAT; зарубежная карта для лондонской или американской площадки не нужна.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Хватит ли NATS 512 МБ RAM в проде?
Для core-режима без JetStream — да, с запасом. Как только включаете персистентность стримов, 512 МБ становится тесно почти сразу: даже небольшой file-стрим с активными консьюмерами быстро выбирает лимит через накладные расходы на индексы и ack-трекинг.
NATS легче RabbitMQ или Kafka по памяти?
Базовый footprint core-процесса ощутимо ниже за счёт отсутствия внешних зависимостей (Erlang VM у RabbitMQ, JVM и Zookeeper/KRaft у Kafka). Но сравнение честно только для голого брокера; с включённым JetStream и репликацией разрыв сокращается, потому что расход начинает определяться данными и Raft-группами, а не ядром сервера.
Чем memory-стрим отличается от file-стрима по требованиям к RAM?
Memory-стрим держит все сообщения до max_bytes прямо в RAM — быстрее, но лимит нужно закладывать буквально. File-стрим пишет данные на диск, в памяти остаются только индексы и состояние консьюмеров — легче по RAM, но зависит от page cache ОС при активном чтении.
Как понять, что кластеру не хватает памяти, а не диска?
Смотрите /jsz на каждой ноде: поле memory покажет расход JetStream отдельно от storage. Если сервер уходит в своп или под OOM-killer при нормальной загрузке диска — дело в RAM, чаще всего в числе Raft-групп на нагруженном кластере, а не в объёме хранимых сообщений.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →