A community member asked whether it is good practice to shard JetStream streams, subjects, or consumers when a single stream starts approaching a large number of durable pull consumers, such as 10,000.
In general, you should plan for sharding or another design change before a single JetStream stream becomes responsible for a very large number of independent durable consumers.
That does not mean you must immediately shard the moment you see a specific consumer count. The right decision depends on your workload, message size, storage, replica count, retention settings, consumer lag, hardware, and operational tolerance for migration risk.
A practical position is:
A JetStream stream is a unit of storage and replication. Adding more servers to a cluster can improve overall system capacity, but a single stream is still not the same as many independent partitions spread across the system. A replicated stream is coordinated by one leader at a time, so replication is primarily about availability and consistency for that stream, not unlimited horizontal throughput for it.
Durable consumers also have state. For each durable consumer, the system must track information such as where that consumer is in the stream and what work is pending or acknowledged. With a small number of consumers, that overhead is usually not the dominant cost. With thousands or tens of thousands of independent durable consumers, the background work becomes more significant.
The important point is that this overhead does not scale linearly. Server-side handling of many consumers has been optimized over time, but performance still does not grow linearly with consumer count. A design that looks fine with 10 durable consumers may behave differently with 10,000, especially if those consumers are not all reading at roughly the same pace.
Durable pull consumers are often a good fit when clients want to control when they fetch work. They avoid some of the delivery behavior associated with push consumers, and they can be very useful for worker-style applications.
However, durable pull consumers are still durable consumers. Each one still has server-side state. If every application instance, user, tenant, device, or integration gets its own durable consumer, the system may eventually spend a meaningful amount of CPU and coordination effort just keeping track of all of those independent positions.
If many consumers are effectively doing the same work, consider whether they really need separate durable state. Multiple worker instances can bind to and fetch from a single shared pull consumer, which distributes messages across those workers without giving each one its own durable position. If each consumer truly needs an independent view of the stream, then sharding becomes a more relevant tool.
JetStream writes stream data to storage. Recently written messages may also remain available in memory or operating system cache for a short period. When consumers read messages soon after they are written, the server may be able to serve those messages without going back to disk.
If consumers fall behind, the situation changes. Once messages are no longer available from cache, serving those delayed consumers can require additional disk reads. At small scale this may be unnoticeable. At large scale, many delayed consumers rereading older data can add significant I/O pressure.
A useful mental model is:
The exact cache behavior depends on the server, operating system, workload, and deployment. Do not rely on a fixed cache window as a contract. Instead, test the delayed-consumer case that your application may actually produce.
There is no universal number of durable consumers where a single stream becomes wrong. Instead, watch for the symptoms that indicate the stream or its consumers are becoming the bottleneck.
Useful signals include:
If your benchmark shows I/O is fine today, that is useful evidence. But make sure the benchmark includes the failure modes you care about: slow consumers, delayed consumers, reconnect storms, realistic message sizes, realistic retention, and the same durability settings you use in production.
Sharding does not have to mean one specific architecture. Common approaches include:
Instead of placing all messages on one subject pattern backed by one stream, partition the subject space. For example, route messages by tenant, region, customer, hash bucket, or workload class.
This can let you create multiple streams, each responsible for a subset of subjects. It also gives you a clearer path to placing different workloads on different resources.
Create multiple streams that each hold part of the data. This can help distribute storage, replication, and consumer state. It can also limit the blast radius of a hot shard or a large group of lagging consumers.
The tradeoff is application complexity. Producers and consumers need to know how messages are assigned to streams, and operations teams need to monitor more moving parts.
Sometimes the better answer is not more streams, but fewer independent durable consumers. If many consumers are equivalent workers, they may be able to share a work distribution pattern instead of each maintaining an independent durable position.
This is workload-specific. If each tenant or user must independently replay and acknowledge messages, reducing consumer count may not be acceptable. But if the consumers are just competing workers, separate durable consumers may be unnecessary overhead.
At some scale, the right boundary may be a separate account, domain, or cluster. That is a larger operational decision and should be driven by isolation, ownership, capacity, and failure-domain requirements, not only by consumer count.
If sharding is a breaking change, it is reasonable to avoid doing it prematurely. A single-stream design can be acceptable when:
The risk is waiting until the stream is already saturated. Sharding under production pressure is usually harder than adding a shard-aware abstraction earlier.
A benchmark should look like the production workload you are worried about, not only the happy path.
Include tests for:
A benchmark where all consumers stay perfectly caught up may miss the cost that appears when consumers drift behind and older messages must be read again from storage.
For a large number of durable pull consumers on one stream, the main question is not whether 10,000 is always too many. The better question is whether the architecture assumes one stream can carry unbounded independent consumer state.
Use this decision framework:
A single stream can be a good starting point, but it is not a substitute for partitioning when the workload grows into many independent, lagging, or high-volume consumers. Shard when the operational and performance risks of centralizing everything in one stream become greater than the migration cost.
Want help from the NATS experts? Meet with our architects to get help tailored to your use case and environment.



News and content from across the community