Lesson 3 · Core NATS & the JetStream model
JetStream & streams
The durable store behind the subject — what a stream is, how it decides when to delete a message, and who creates it in this repo.
Your win: explain what a JetStream stream is, read a real stream config, and say why this repo chose interest retention — and who provisions streams.
A stream is a durable store for subjects
Turn on JetStream and a stream can capture the messages published to a set of subjects into a durable, ordered log. Now a message survives even if no consumer is listening yet — it waits in the stream until a consumer reads and acknowledges it. That's the whole leap from at-most-once to at-least-once.1 A stream has four things worth knowing: its subjects, a retention policy, a storage type, and a replica count.
Retention: when a message is deleted
Streams don't keep messages forever — the retention policy decides when they go:2
| Policy | Deletes a message when… |
|---|---|
| Limits (default) | a limit is hit — max age, max bytes, or max messages |
| WorkQueue | a consumer acks it (a strict FIFO queue; one consumer) |
| Interest | every interested consumer has received it (kept only while someone still needs it) |
Staff.Upserted; conversationmgmt and others consume it. Interest
retention keeps each message exactly as long as a registered consumer hasn't taken it, then drops
it — no unbounded log, no manual cleanup, but still durable through a subscriber restart. It's the
natural fit for "deliver this event to the services that care, then forget it," which is most of
the repo's NATS traffic.
A real stream config
&nats.StreamConfig{
Name: constants.StreamNotification, // "notification"
Retention: nats.InterestPolicy, // keep only while a consumer still needs it
Subjects: []string{"Notification.*"}, // captures all Notification.* events
Replicas: 3, // three copies for fault tolerance (RAFT)
MaxBytes: 4 GB, // a size limit still applies on top
}
- Storage is the default here — file (durable, survives
restarts), set in
nats.conf. Memory storage would be faster but volatile. - Replicas: 3 — the stream is copied across three NATS servers via a RAFT quorum, so a node failure doesn't lose it (forced to 1 in local dev to save resources).2
- The limits (like
MaxBytes) always apply on top of the retention policy — a safety ceiling.
Who creates streams: the fink job
Streams aren't created by the services that use them — they're provisioned centrally by the fink job, which collects every service's stream configs and upserts them:
upsertStreams: for every declared stream config → jsm.UpsertStream(cfg)
// UpsertStream is a hand-rolled idempotent upsert (vanilla NATS has no such helper):
if isLocal { cfg.Replicas = 1 }
if stream exists && retention differs → DELETE + re-ADD // retention can't be changed in place
else if subjects/replicas/maxAge/maxBytes differ → UpdateStream
else if not found → AddStream
fink (a deploy-time job). Consumers
(Lesson 4) are provisioned at subscribe time by the golib, when a service registers its
subscriber. So a stream is shared infrastructure; a consumer belongs to a subscriber. (Note:
conversationmgmt owns no streams — it's subscriber-only, reading streams that
usermgmt and others provisioned.)
JetStream streams
Retention policies, storage, replicas, and limits — the config you just read, in depth.
→ docs.nats.io — Streams
→ JetStream model deep dive (retention & limits interplay)
Check yourself (from memory)
Q1. What does a JetStream stream add over core NATS pub/sub?
Q2. Why does this repo use InterestPolicy retention?
Q3. Who provisions streams, and when?
fink collects every service's stream configs and upserts
them. Consumers, by contrast, are created at subscribe time.
UpsertStream = idempotent, delete+recreate on retention drift). Consumers are provisioned
separately at subscribe time. conversationmgmt owns no streams (subscriber-only).QueueSubscribe your services actually call. Ask me about the retention
policies if the interest model is new.
2. docs.nats.io — Streams. In-repo: cmd/server/fink/streams/notificationmgmt.go, jetstream.go:360-403.