JetStream memory pressure means a NATS server’s memory-backed stream usage has reached or exceeded 90% of the reserved JetStream memory (max_mem). At this level, the server is approaching the hard limit where new writes to memory-backed streams will be rejected outright.
Memory-backed streams in NATS JetStream store all messages in RAM for the lowest possible read and write latency. The trade-off is that every byte of message data directly consumes server memory, bounded by the max_mem reservation configured for JetStream on that server. When usage crosses 90%, you are one traffic spike away from hitting the ceiling.
When a server exhausts its JetStream memory reservation, all publish operations to memory-backed streams on that server fail with a “no space” error. Unlike file-backed storage, there is no slow degradation — the transition from “working” to “rejecting writes” is binary and abrupt. Publishers receive errors, request-reply chains break, and any system that depends on memory-backed streams for low-latency data flow stops making progress.
The risk compounds in clustered environments. Memory-backed stream replicas consume memory on every server that hosts a replica. An R3 memory-backed stream consuming 500 MiB uses 500 MiB on three separate servers. If all three servers are running near their memory limit, a single burst of publishes can push multiple servers past their threshold simultaneously, causing a cluster-wide write failure for all memory-backed streams — not just the one that triggered the pressure.
Memory-backed streams growing beyond expectations. A stream was created with storage: memory for low-latency access, but no max_bytes or max_age limit was set. Messages accumulate without bound until the server’s max_mem reservation is exhausted. This is the most common cause.
Traffic spike to memory-backed streams. A burst of publishes — batch imports, backfills, or incident-driven traffic — fills memory faster than retention policies can expire old messages. The stream’s retention limits may be correctly configured for steady-state, but inadequate for peak load.
Over-provisioned replica count. An R3 memory-backed stream consumes 3x the memory of an R1 stream for the same data. Over-replicating memory-backed streams when high availability isn’t required wastes the scarcest resource in the cluster.
Undersized max_mem reservation. The server’s JetStream memory reservation was set conservatively during initial deployment and hasn’t been increased as workloads grew. The reservation should reflect actual peak memory needs, not initial estimates.
Large deduplication windows on high-throughput streams. The deduplication map for Nats-Msg-Id headers is held in memory. A 2-minute dedup window at 50,000 msg/s with UUID-based message IDs can consume hundreds of megabytes of memory that counts against max_mem.
nats server report jetstreamLook at the MEM and MEM MAX columns. The ratio tells you how close each server is to its memory reservation. Any server above 90% triggers this check.
nats stream reportSort by memory usage to find the largest memory-backed streams. Pay attention to streams with storage: memory — file-backed streams don’t contribute to max_mem pressure.
nats stream info <stream_name>Look at:
# Check the JetStream advisory subjects for resource pressure eventsnats sub '$JS.EVENT.ADVISORY.>'If you have Prometheus monitoring.
Purge non-critical memory-backed streams. If any memory-backed stream contains data that can be regenerated or isn’t time-sensitive, purge it:
nats stream purge <stream_name>Set retention limits on unbounded streams. If a memory-backed stream has no limits configured, add them immediately:
nats stream edit <stream_name> --max-bytes 256MiB --max-age 1hConvert large streams to file-backed storage. Most streams that use storage: memory don’t actually need sub-millisecond latency. File-backed streams with SSD storage provide single-digit millisecond latency — fast enough for the vast majority of workloads:
1// When creating a new stream, use file storage unless2// you have a measured latency requirement for memory3js, _ := nc.JetStream()4_, err := js.AddStream(&nats.StreamConfig{5 Name: "ORDERS",6 Subjects: []string{"orders.>"},7 Storage: nats.FileStorage, // not MemoryStorage8 MaxBytes: 1 << 30, // 1 GiB9 MaxAge: 24 * time.Hour,10})1import nats2from nats.js.api import StreamConfig, StorageType3
4nc = await nats.connect()5js = nc.jetstream()6
7await js.add_stream(StreamConfig(8 name="ORDERS",9 subjects=["orders.>"],10 storage=StorageType.FILE, # not MEMORY11 max_bytes=1 << 30, # 1 GiB12 max_age=86400, # 24 hours13))Reduce replica count on non-critical memory-backed streams. If a memory-backed stream is R3 but the data is ephemeral or easily regenerated, reduce to R1:
nats stream edit <stream_name> --replicas 1Increase the max_mem reservation. If the server has physical memory headroom, increase the JetStream memory reservation in the server configuration:
1jetstream {2 max_mem: 4GiB # increased from 2GiB3}Reload the configuration without restart:
nats-server --signal reloadEnforce max_bytes on all memory-backed streams. Use account-level JetStream limits to cap total memory usage per account, and require max_bytes on every stream:
1accounts {2 PRODUCTION {3 jetstream {4 max_mem: 1GiB5 max_disk: 100GiB6 }7 }8}Default to file storage. Adopt a team convention: memory-backed streams require explicit justification (measured latency requirements). Everything else uses file storage. This prevents accidental memory pressure from streams that don’t need the performance characteristics of RAM.
Monitor with Insights. Synadia Insights evaluates JetStream memory pressure every collection epoch across all servers. When pressure reaches 90%, this check fires as a warning. If memory continues climbing to the critical threshold, JETSTREAM_007 (JetStream Memory Utilization Critical) fires with critical severity — at that point, writes are failing or about to fail.
All publish operations to memory-backed streams on that server are rejected with a “no space” error. Existing messages remain accessible for reading. The server itself continues operating — file-backed streams, core NATS, and cluster routes are unaffected. Only memory-backed stream writes fail.
No. max_mem is exclusively the JetStream memory reservation for stream data. The NATS server process itself uses additional memory for connection buffers, subscription routing tables, Raft state, and internal bookkeeping. Total server memory consumption is always higher than max_mem. Plan for at least 2x max_mem in available physical memory.
Yes, but selectively. Memory-backed streams are ideal for high-frequency, low-latency data that is either ephemeral or easily regenerated — real-time pricing feeds, session state, caching layers. For anything where durability matters or message volume is unpredictable, file-backed storage is the safer default. SSD-backed file streams provide single-digit millisecond latency, which satisfies most workloads.
Monitor the ratio of used to reserved JetStream memory via the /jsz monitoring endpoint or Prometheus.
Alert at 85% to give yourself a buffer before the 90% warning threshold. Synadia Insights evaluates this automatically every epoch across your entire deployment.
Not in place — you cannot change the storage type of an existing stream. The migration path is: create a new file-backed stream, set up a mirror or source to replicate data from the old stream, switch publishers and consumers to the new stream, then delete the old memory-backed stream. This can be done with zero message loss using JetStream sourcing.
With 100+ always-on audit Checks from the NATS experts, Insights helps you find and fix problems before they become costly incidents.
No alert rules to write. No dashboards to maintain.
News and content from across the community