This episode covers JetStream performance: high-throughput publishing in version 2.14+, batching for bulk publishing, server-side message scheduling, scaling with replication and partition across subjects, storage tuning, and benchmarks toward millions of messages per second.

NATS is known as the fastest messaging system, but speed without the right techniques is still wasted. Episode 16 opens the JetStream performance toolbox: how to publish millions of messages per second without overloading the server, and how to design streams so they stay fast as data grows.
We'll cover high-throughput publishing in version 2.14+, batching, server-side message scheduling, scaling via replication and partitioning, storage tuning, and real benchmarks.
Since NATS Server 2.14, publishing to JetStream has a first-class high-throughput path: messages are buffered on the server and written to disk in groups, not one by one. This removes the per-message fsync bottleneck.
nats-server -c nats.conf --jetstream --store_dir /data/jsThe nats-server --jetstream --store_dir /data/js command uses fast SSD disk storage. For high throughput, make sure the store directory sits on a fast local disk, not network storage.
On the client side, use asynchronous publishing with periodic flushing:
nc, _ := nats.Connect("nats://localhost:4222")
js, _ := nc.JetStream()
for i := 0; i < 100000; i++ {
js.PublishAsync("orders.created", []byte("data"))
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(30 * time.Second):
}js.PublishAsync("orders.created", []byte("data")) sends without waiting for a per-message ack, and js.PublishAsyncComplete() waits until everything is sent. Async publishing lets thousands of messages flow through a single pipeline without a round-trip per message.
Batching reduces network overhead by sending many messages at once:
async with await js.publish_batch([
js.create_msg("orders.created", b"pesan-1"),
js.create_msg("orders.created", b"pesan-2"),
js.create_msg("orders.created", b"pesan-3"),
]) as batch:
await batch.wait_for_all(timeout=30)js.publish_batch([...]) sends many messages as one operation. For data pipelines, batching drastically improves throughput compared to publishing one at a time.
Version 2.14 added server-side message scheduling: the server controls when consumers receive messages, so many consumers can share one stream without competing for bandwidth.
nats consumer add ORDERS PROCESSOR --push --flow-control --ack explicitThe --flow-control flag enables server-side flow control. The server slows down delivery when a consumer can't keep up, preventing overload that leads to pending backlog.
Info
Flow control and heartbeat work together: the server sends heartbeats to make sure the connection is alive and delays messages if a consumer is full. Enable both for push consumers in production.
For higher throughput, divide the load:
nats stream add ORDERS_EU --subjects "orders.eu.>"
nats stream add ORDERS_ASIA --subjects "orders.asia.>"The nats stream add ORDERS_EU --subjects "orders.eu.>" command partitions data per region. Each stream is managed and scaled on its own — this architecture is called subject-based sharding.
A few tunings with big impact:
max_age and max_bytes so streams don't grow out of control.storage: file
max_age: 7d
max_bytes: 4GiB
replicas: 3
compression: trueThe compression: true block enables message compression to save disk. Combining limits and compression keeps write performance stable.
NATS provides the nats bench benchmarking tool:
nats bench --pub 10 --sub 10 --size 256 --msgs 1000000 benchmark.subjectThe nats bench --pub 10 --sub 10 --size 256 command runs 10 publishers and 10 subscribers sending 1 million 256-byte messages. The output shows messages per second and latency. NATS often shows millions of messages per second in benchmarks like this — depending on hardware and configuration.
publish: 2,400,000 msg/sec
deliver: 2,350,000 msg/sec
latency: p50 1.2ms, p99 3.8msThe publish: 2,400,000 msg/sec result above shows millions of messages per second throughput with millisecond latency. Remember: benchmarks are guidance, not promises — always test with your real workload patterns.
Episode 16 optimized JetStream: high-throughput publishing in version 2.14 with server-side buffering, async publishing and batching on the client, server-side message scheduling with flow control, scaling via partitioning across subjects and storage tuning, and benchmarks to measure real capability.
Key takeaways:
nats bench measures throughput and latency as guidance.In episode 17 next, we'll discuss NATS on Kubernetes — deploying with Helm charts and nats-operator, StatefulSets for JetStream with PVC-backed persistence, then KEDA NATS JetStream scaler integration for event-driven autoscaling, Argo workflows, and service meshes. Your NATS is ready to go cloud-native.