A common community question is how to model a dashboard where each user watches a dynamic, overlapping set of subject filters over a large JetStream-backed data set.
For example: one stream contains thousands of unique subjects, millions of messages per day, and dashboards may create per-user filters such as 10–20 wildcard subject patterns. Each user needs an initial snapshot of the latest value for the subjects they care about, then live updates as new values arrive.
The short answer: use JetStream for durable state and snapshot reads, and use core NATS subscriptions for live fan-out whenever possible. Keeping a long-lived JetStream consumer per dashboard user can work for a proof of concept, but it is usually not the first design to scale.
JetStream consumers are server-side resources. They track delivery state, pending messages, acknowledgements, redelivery behavior, and filter configuration. Ephemeral consumers reduce lifecycle management compared with durable consumers, but an active ephemeral consumer still has work to do while it exists.
If you have 1,000 dashboard users, each with 10–20 highly overlapping wildcard filters, a design based on active per-user JetStream pull consumers can create several scaling concerns:
That does not mean the model cannot work. It means the answer depends on message rate, subject cardinality, filter selectivity, replicas, storage, client fetch cadence, acknowledgement policy, and hardware. For a proof of concept, test it directly. For a production design, consider separating snapshot retrieval from live delivery.
A common pattern is:
This keeps JetStream focused on persistence and replay, while core NATS handles the real-time fan-out path.
Core NATS subscriptions are intentionally lightweight. If a dashboard user needs 20 wildcard filters, creating 20 core subscriptions for that user is often a better scaling shape than keeping one active JetStream consumer per user. You should still load test your expected user count and filter count, but core NATS is the part of the system designed for high-throughput pub/sub fan-out.
For odds, prices, status, presence, and similar monitoring use cases, the dashboard often needs the latest value per market or instrument, not every historical update.
If that matches your requirement, configure the stream so it retains only a bounded number of messages per subject. In JetStream configuration this is commonly done with MaxMsgsPerSubject. Setting that value to 1 means the stream keeps the latest message for each subject, subject to the rest of your stream limits and retention configuration.
Conceptually:
1odds.sport.league.market.selection -> latest known oddsWith that model, an initial dashboard load can query JetStream for the latest values matching the user’s subject filters instead of replaying the full history of odds changes.
Where supported by your server and client version, a consumer deliver policy for the latest message per subject can also be useful for snapshot-style reads. Check the JetStream consumer options available in your client library and validate the behavior with your exact stream configuration.
For the snapshot phase, create an ephemeral pull consumer scoped to the user’s requested filter or filters, fetch the initial data, then remove the consumer or let it go away when no longer needed.
One prerequisite to confirm first: carrying more than one subject filter on a single JetStream consumer requires a reasonably recent NATS server, since multiple filter subjects on one consumer were introduced in NATS server 2.10. On older servers a consumer accepts only a single filter subject, so a user with many distinct filters would need either a separate consumer per filter or one broader filter that the application narrows further. If you plan to build a snapshot for 10–20 filters with a single consumer, verify your server version supports it.
The goal is to avoid keeping snapshot-specific JetStream state around for the entire dashboard session.
A practical flow looks like this:
1User opens dashboard2 -> determine requested subject filters3 -> create ephemeral JetStream consumer for snapshot4 -> fetch latest retained values matching those filters5 -> render initial dashboard state6 -> stop/delete snapshot consumer7 -> subscribe with core NATS for live updatesThis approach also makes it easier to tune the expensive path. Snapshot reads happen at user connect time or when the filter set changes. Live updates use normal NATS subscriptions.
A core NATS subscription is made to a subject pattern. If a user has 10–20 wildcard filters, expect to create 10–20 subscriptions and route them to the same application handler or channel.
For example, conceptually:
1subscribe odds.tennis.>2subscribe odds.football.premier.*3subscribe odds.basketball.nba.>Some tools may accept multiple subjects in one command-line invocation, but client APIs commonly model these as separate subscriptions internally. In a .NET application, plan around one subscription per subject filter unless the specific API you are using documents a different abstraction.
That is usually acceptable. The important comparison is not “one subscription versus many subscriptions”; it is lightweight core NATS subscriptions versus stateful JetStream consumers for live fan-out.
JetStream republish can be useful when you want messages persisted to a stream and then emitted again onto core NATS subjects for subscribers.
In this design, republish can help separate the durable ingest path from the live subscription path:
1Publisher2 -> JetStream stream stores update3 -> republish emits update onto live subject4 -> dashboard core NATS subscribers receive updateHowever, republish does not eliminate the need for user-level filtering. If each dashboard user has dynamic wildcard filters, those filters still belong in the subscriptions the dashboard service or client creates. Republish is a stream-level routing tool, not a per-user dynamic query engine.
Whether you need republish depends on your ingest topology:
Before deciding whether 1,000 users is safe for your environment, test the specific shape you plan to run.
Useful measurements include:
Also test operational events:
The right answer is workload-dependent, and these tests will reveal whether the bottleneck is snapshot reads, live fan-out, storage, client behavior, or subject/filter design.
For a dashboard that needs an initial latest-value snapshot and then live updates:
MaxMsgsPerSubject when only the latest value per subject is required.This keeps each part of NATS doing the job it is best suited for: JetStream for persistence and replay, core NATS for low-overhead real-time fan-out.
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