Where to go next
This chapter started with a publisher and no one guaranteed to be
listening. It ends with an ORDERS stream, a handful of consumers
reading it at their own pace, and a mirror keeping a permanent copy.
This page gathers the model you built into one place. It then points you at the chapters and Reference that take it further.
The core model
Every page in this chapter built on the same three ideas.
A stream is a log of messages kept on the server. You publish to a subject. The server adds the message to any stream that captures that subject. The message stays there, ready to be read again, until a limit removes it.
A consumer is a position marker over a stream. It tracks which messages a reader has seen, separately from every other consumer. Two consumers on the same stream can read the same messages at different positions without affecting each other.
An ack is the reader's way of saying a message is handled. Until the consumer acks, the message stays open, and the server sends it again after a timeout. A stored message has not yet been processed.
Everything else in this chapter builds on stream, consumer, and ack. That includes filtering, worker pools, retention, and mirrors.
Where the details live
This chapter teaches the ideas and is not tied to a server version. The exact flags, defaults, and ranges live in Reference, which is tied to a version and covers every option. When you need the precise type of a config field, or the full list of consumer options, look there.
The Reference root is the way in. The pointers to Reference throughout this chapter all lead into it.
Sibling deep dives
This was the first deep dive. The others build on the same foundation, so the stream, consumer, and ack model carries into them.
The Key-Value deep dive shows how a key-value bucket is a stream underneath. The keys become subjects. The history becomes sequence numbers. A watch is a consumer. Everything you learned about retention and limits applies directly.
The Object Store deep dive does the same for large files. An object is split across many messages in a stream, then put back together when you read it. The stream is still where the data lives.
The Clustering & Replication deep dive goes deeper
than this chapter's single page on surviving node loss. It covers how
R=3 picks a leader, how the server decides where to place the stream,
and what happens when a node fails.
The Monitoring deep dive covers how to watch a stream and its consumers in production. It walks through advisories, health endpoints, and the numbers that tell you a consumer is falling behind.
The Backup & Recovery deep dive covers the day-to-day operations: saving a stream to a snapshot, restoring it, and using the mirrors you met on the Mirrors and sources page to recover from a disaster.
Where you are
This is the end of the chapter. This page adds nothing new to the running
example. The ORDERS stream, its consumers, and its mirror are still
running in your session as you left them on the previous page. You can
keep experimenting with them, or remove them with nats stream rm ORDERS when you're done.
You now have the core model. A stream stores messages. A consumer reads them at its own pace. An ack confirms a message was handled. That model sits under every other JetStream feature.
Production checklist
Every page in this chapter closed with a Pitfalls section. This gathers the action items from all of them in one place. Each group links back to the page that explains it.
Your first stream — see Pitfalls
- Stay on plain pub-sub when the next message supersedes the last; reach for a stream only when a missed message has consequences.
- Set at least one limit (
MaxAge,MaxBytes, orMaxMsgs) so an unbounded stream never fills the disk. - Pick the stream name deliberately the first time; there's no rename, only delete-and-recreate.
- Choose the retention policy before messages flow; switching to or from WorkQueue on a live stream is rejected.
Publishing — see Pitfalls
- Read the
PubAckback; a plainnats publine isn't proof the message was stored. - Give every retryable publish a stable
Nats-Msg-Idso a retry doesn't double-store. - Wait for delivery and ack before acting on a business outcome; a
PubAckmeans stored, not processed.
Reading back — see Pitfalls
- Reach for
--allonly when you want the whole history; sample the tail with--last,--since, or--start-sequence. - Use a named, durable consumer for any read you must resume after a disconnect; an ephemeral one restarts from sequence 1.
- Confirm
--allversus--newmatches the question (backlog or live traffic) before you run the command. - Pair
--allwith--terminate-at-endfor a one-shot replay; on its own it drains the backlog then blocks waiting for more.
Delivery and acknowledgment — see Pitfalls
- Set Ack Wait longer than your slowest handler, with headroom, to avoid a redelivery storm.
- Ack on every success path, and term a genuinely unprocessable message so it stops coming back.
- Ack each delivery exactly once, in one place in the handler.
- Edit a durable consumer to change it; recreating with a new config is rejected as already-exists.
Filtering — see Pitfalls
- Confirm the filter matches a subject the stream stores before assuming an empty pull means an empty stream.
- Decide retention through stream limits, not filters; a filter narrows a view, it never deletes messages.
- Keep multiple filter subjects on one consumer disjoint; the server rejects overlap inside a single consumer.
Acknowledgment — see Pitfalls
- Nak a transient failure with a delay, or set a backoff, instead of a bare nak that loops at network speed.
- Term a poison message the moment the code knows no attempt will succeed, rather than burning the delivery budget.
- Subscribe to the max-deliveries advisory so a dropped message doesn't vanish unnoticed; JetStream has no dead-letter queue.
- Raise AckWait or send in-progress for long jobs so a slow handler doesn't trigger double work.
Pull consumers — see Pitfalls
- Treat an empty fetch as "nothing right now" and loop; never as an error that crashes the worker.
- Always set an
expireson a fetch so a quiet stream returns control instead of stalling. - Keep
MaxAckPendingat or above your batch size so it doesn't throttle throughput. - Pair
batchwithmax_bytesso a single pull is bounded by size as well as count.
Scaling a consumer — see Pitfalls
- Key every side effect by
order_idso a redelivered message is a no-op, not a double shipment. - Size
MaxAckPendingto at least your worker count, with headroom; the cap is shared across the whole pool. - Tune
AckWaitto real processing time so a crashed worker's message recovers without redelivering honest work.
Priority groups — see Pitfalls
- Run one priority group per consumer; passing more than one silently uses only the first.
- Drive failover with
min_pendingormin_ack_pending; the ADR-42failovertimer isn't shipped yet. - Lean on explicit acks and idempotent handlers, not the pin, to keep work from doubling up; the pin is not a lock.
- Keep each pull's
expirescomfortably under--pinned-ttlso a pinned client renews in time.
Pausing a consumer — see Pitfalls
- Read the
Paused Until Deadlineline before debugging a "stuck" consumer; a pause looks like a stall. - Pause with a duration like
1hso the deadline can never land in the past and no-op. - Size the stream for the longest pause you expect; publishes keep landing and count against the limits while a consumer sleeps.
Shaping the stream — see Pitfalls
- Switch to Discard New when you need backpressure; Discard Old drops the oldest message silently.
- Size
MaxBytesfor your peak, not your average, when the age window matters; either limit can fire first. - Add
MaxMsgsPerSubjectwhen each subject deserves its own retention; whole-stream limits let one noisy subject starve a quiet one.
Retention policies — see Pitfalls
- Create a new stream to move to or from WorkQueue; the server locks that change on a live stream.
- Give each WorkQueue consumer a disjoint filter, or share one consumer as a pool; overlapping consumers are rejected.
Per-message TTL — see Pitfalls
- Confirm
Allows Per-Message TTLis on before relying onNats-TTL; a header on an opted-out stream fails the publish. - Size a TTL to outlast the slowest healthy consumer's lag; the clock deletes the stored copy whether or not it was read.
- Set
SubjectDeleteMarkerTTLonly when consumers must learn a value expired, and keep it at or below your shortest TTL.
Altering stream state — see Pitfalls
- Use
nats stream purgeto clear messages andnats stream rmto delete the stream; don't swap the two. - Treat a sequence as a stable address that may hold no message; never assume messages run
1..Nwithout gaps. - Set
DenyPurgeon a stream that matters, and keep a mirror; a purge can't be undone.
Surviving node loss — see Pitfalls
- Confirm the replica count before trusting a stream with real orders; R=1 has no copy to recover from.
- Use odd replica counts: R=3 for the production floor, R=5 for state you can't re-derive.
- Prove failover on a real cluster; a green single-node run can't show leader election or a node loss.
Mirrors and sources — see Pitfalls
- Publish to the upstream stream, not the mirror; a mirror captures no subjects and replies
no responders. - Read the
Lagfield before assuming a mirror is current; a mirror is eventually consistent, not synchronous. - Pick
filter_subjectorsubject_transformson one entry, never both; the server rejects a config that sets both. - Verify each export type for cross-domain sourcing; a wrong type lets the mirror silently never catch up.
See also
- Reference — every config field, flag, default, and error code, versioned and exhaustive.
- Key-Value deep dive — the next chapter built on the same stream foundation.
- Clustering & Replication deep dive — the detail behind "surviving node loss."