Skip to main content

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, or MaxMsgs) 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 PubAck back; a plain nats pub line isn't proof the message was stored.
  • Give every retryable publish a stable Nats-Msg-Id so a retry doesn't double-store.
  • Wait for delivery and ack before acting on a business outcome; a PubAck means stored, not processed.

Reading back — see Pitfalls

  • Reach for --all only 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 --all versus --new matches the question (backlog or live traffic) before you run the command.
  • Pair --all with --terminate-at-end for 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 expires on a fetch so a quiet stream returns control instead of stalling.
  • Keep MaxAckPending at or above your batch size so it doesn't throttle throughput.
  • Pair batch with max_bytes so a single pull is bounded by size as well as count.

Scaling a consumer — see Pitfalls

  • Key every side effect by order_id so a redelivered message is a no-op, not a double shipment.
  • Size MaxAckPending to at least your worker count, with headroom; the cap is shared across the whole pool.
  • Tune AckWait to 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_pending or min_ack_pending; the ADR-42 failover timer 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 expires comfortably under --pinned-ttl so a pinned client renews in time.

Pausing a consumer — see Pitfalls

  • Read the Paused Until Deadline line before debugging a "stuck" consumer; a pause looks like a stall.
  • Pause with a duration like 1h so 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 MaxBytes for your peak, not your average, when the age window matters; either limit can fire first.
  • Add MaxMsgsPerSubject when 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 TTL is on before relying on Nats-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 SubjectDeleteMarkerTTL only when consumers must learn a value expired, and keep it at or below your shortest TTL.

Altering stream state — see Pitfalls

  • Use nats stream purge to clear messages and nats stream rm to 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..N without gaps.
  • Set DenyPurge on 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 Lag field before assuming a mirror is current; a mirror is eventually consistent, not synchronous.
  • Pick filter_subject or subject_transforms on 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