Queue vs log vs pub/sub, BullMQ, RabbitMQ and SQS vs Kafka and JetStream
A queue forgets work once it is done; a log keeps history for every reader; pub/sub keeps nothing.
Predict first
Before you read: predict
A ticket shop publishes six order events to a JetStream stream with 7-day limits retention. The billing service's consumer has received all six and acknowledged four of them. Then a new consumer for analytics is created, set to start from the first message.
How many of the six does analytics receive?
The 30-second version
Pub/sub pushes each message to the subscribers connected at that moment and stores nothing. A queue hands each message to one consumer and stops delivering it once acknowledged. A log keeps messages until a retention limit (age, size or count) drops the oldest, whatever was read, and each consumer group has its own position, so new groups can replay.
A queue forgets work once it is done; a log keeps history for every reader; pub/sub keeps nothing.
How it works
Step 1 of 14
In the kitchen
- the crier's call: whoever is in the square hears it, anyone at home misses it → pub/sub (steps 1–2)
Analogy: A village's three ways of passing news
The town crier calls it in the square, odd-job cards wait in a box at the shop, and the parish clerk keeps a numbered minute book.
| In the village | In the system |
|---|---|
| the crier's call: whoever is in the square hears it, anyone at home misses it | pub/sub (steps 1–2) |
| an odd-job card taken by one helper and torn up when the job is reported done | a queue and its ack (step 3) |
| a card not reported done by evening goes back into the box | redelivery (step 4) |
| numbered minute-book entries: reading one leaves it in the book | a log (step 5) |
| full minute books are pulped after 30 days, read or not | retention (step 6) |
| each society's own bookmark; a new society starts at page one | a consumer's position (step 7) |
| one minute book per committee, each read by one member of a reading group | partitions (steps 9–10) |
In real terms: before choosing a broker, write down who must see each message (one worker, every service, whoever is listening) and whether anyone joining later needs the past.
Breaks down when: you picture the minute book as the only copy. A log's readers are not taking turns with the book; any number read it at once, each at its own page.
Contrasting case: one service with a work queue and nobody else interested. A queue and a work-queue stream behave alike there; the choice only matters once a second reader or a late reader appears.
Examples, run for real
4.1 ExamHall: the proctoring event stream
EXH-1360: proctoring events went out on a core NATS subject. The audit service, deployed later, found nothing to back-fill. MR !1560 moves them to a JetStream stream kept 30 days, with one durable consumer per service. Grading jobs stay on BullMQ.
4.1 core subject anti-cheat 5 · analytics 5 · audit (subscribed after #5) 0
4.1 JetStream anti-cheat #1–#5 · analytics #1–#5 · audit (created after #5, deliver all) #1–#5 · stream holds 5
The subject delivered only to subscribers present at the time (step 2). The stream kept all five, and each consumer read them from its own position (steps 5, 7).
4.2 The three shapes, side by side
Six messages, readers A and B; in two rows B is away while #3 and #4 are published.
4.2 core, two subscribers A 6 · B 6 · deliveries 12
4.2 core, queue group of two deliveries 6 · distinct 6 · to both 0 · split between A and B varies
4.2 core, B away during #3–#4 A 6 · B 4 (#1–#2, #5–#6) · B missed #3–#4
4.2 JetStream, B's durable, away B 6 (#1–#6) · missed (none) · order 1 2 3 4 5 6
4.2 JetStream, one durable, 2 workers deliveries 6 · distinct 6 · left 0
Plain subscribers each get everything; a queue group (subscribers sharing one group name, one member per message) splits them (step 8), but keeps nothing for the absent (step 2). A durable consumer, which the server keeps by name with its position, resumes where it stopped.
4.3 Redelivery and order
Message #2 is rejected (nak) on its first delivery; everything else is acknowledged. max_ack_pending caps how many delivered messages may await an ack at once.
4.3 max_ack_pending 5 deliveries #1 #2(nak) #3 #4 #5 #2(again) · handled in order: #1 #3 #4 #5 #2
4.3 max_ack_pending 1 deliveries #1 #2(nak) #2(again) #3 #4 #5 · handled in order: #1 #2 #3 #4 #5
With five in flight, #2 comes back after #5 (step 12). With one in flight, order holds, at the cost of throughput.
4.4 model: 3 partitions, group of 5 assigned m1:p0 m2:p1 m3:p2 · idle m4, m5
4.4 model: order per key acct7 read as o1 o3 o6 (partition 0, one reader)
4.4 model: second group from offset 0 reads 6 of 6 · first group's offsets 3/2/1 unchanged by it
4.4 model: o3 fails, retried in place: partition 0 offset stays 1 after 3 tries · o6 behind it read: false · partitions 1–2 read 3 of 3
The model encodes the quoted Kafka rules: one reader per partition (step 10), order per key (step 9), a group per position (step 7), and one offset per partition (step 11).
Try it
Answer first, then check. Nothing is saved.
Question 1 of 5
A chat app shows typing indicators ("Ana is typing…") to the users on each of its three chat servers. Every server must show each indicator while it is current. A server that restarts must not replay indicators from before its restart, which would show people typing who stopped long ago.
Which setup fits?
Question 2 of 5
A Kafka payments topic has 4 partitions and is keyed by merchant ID; a classic consumer group of 4 instances reads it. One merchant sends most of the payments, and its backlog grows. The team scales the group to 8 instances.
How many of the 8 instances read that merchant's payments?
Question 3 of 5
A chat search indexer applies "message edited" events from a JetStream stream; each applied edit overwrites that message's index entry. To keep up, it now runs as two instances that share one durable consumer, which allows up to 10 unacknowledged edits. No index write fails and nothing is nak'd, yet search sometimes shows an older edit than the latest.
Which is the bug?
Question 4 of 5
A ticketing service emails a confirmation for each ticket.issued message. Its three mailers form a core NATS queue group, mailers. During deploys all three restart together, and tickets issued in that window never get an email.
Which change emails every ticket, one mailer per ticket, across a restart of all three?
Question 5 of 5
A catalog's uploads stream uses work-queue retention: four resize workers share one durable consumer, and each image leaves the stream once acked. A search team adds its own durable consumer on the same stream, to index every upload. The server refuses to create it, so search indexes nothing.
Which is the bug?
Go deeper
Pro tips
- List a message's readers before choosing the broker. Write "one worker", "every service", or "whoever is listening", and "must a later reader see the past?"; the answers pick queue, log or pub/sub. When: any new event or job type (steps 3, 5, 14).
- Budget ordering explicitly. If a handler overwrites state, either keep one message in flight, or carry a version in each message and drop stale ones. When: consumers that nak or retry (step 12).
Source
- NATS docs, Core NATS publish-subscribe: https://docs.nats.io/nats-concepts/core-nats/pubsub · fetched 2026-09-28. "A message reaches every interested subscriber that's connected at the moment of publish, at most once. If a subscriber is offline, restarting, or not subscribed yet, it never sees that message. The server does not store it for later." (steps 1–2). Valid as of 2026-09-28.
- NATS docs, Queue groups: https://docs.nats.io/learn/core-nats/queue-groups · fetched 2026-09-28. "For each message, it picks a random index into that list and delivers to that member". Valid as of 2026-09-28.
- NATS docs, Streams and Retention policies: https://docs.nats.io/nats-concepts/jetstream/streams and https://docs.nats.io/learn/jetstream/retention-policies · fetched 2026-09-28. Limits: "Messages stay until a limit is reached:
MaxMsgs,MaxBytes, orMaxAge, whichever comes first"; discard Old: "When the stream finally hits a limit, the oldest messages are deleted to make room", discard New "turns away new messages when the stream is full"; WorkQueue: "The first ack removes the message for everyone", and the server "rejects overlapping consumers outright" (steps 5, 6, 13; t2). Valid as of 2026-09-28. - NATS docs, Reading back and Delivery and acknowledgment: https://docs.nats.io/learn/jetstream/reading-back and https://docs.nats.io/learn/jetstream/delivery-and-acknowledgment · fetched 2026-09-28. "A stream can feed many consumers at once, each reading at its own pace and each with its own position"; "Deliver all starts the consumer at the first message in the stream"; "A redelivery doesn't slot back into stream order... the repeat can arrive after messages with higher sequence numbers"; the ack/redeliver loop gives "at-least-once delivery" (steps 4, 7, 12). Valid as of 2026-09-28.
- Apache Kafka 4.2, Introduction: https://kafka.apache.org/42/getting-started/introduction/ · fetched 2026-09-28. "events are not deleted after consumption"; retention is "a per-topic configuration setting, after which old events will be discarded"; "Events with the same event key (e.g., a customer or vehicle ID) are written to the same partition"; "any consumer of a given topic-partition will always read that partition's events in exactly the same order as they were written" (steps 5, 6, 9; model rules R1, R2, R5). Valid as of 2026-09-28.
- Apache Kafka 4.2, Design: https://kafka.apache.org/42/design/design/ · fetched 2026-09-28. "Our topic is divided into a set of totally ordered partitions, each of which is consumed by exactly one consumer within each subscribing consumer group at any given time"; "The position of a consumer in each partition is just a single integer, the offset of the next message to consume" (steps 10–11; R3, R4). Valid as of 2026-09-28.
- Apache Kafka, 4.2.0 release announcement (2026-02-17): https://kafka.apache.org/blog/2026/02/17/apache-kafka-4.2.0-release-announcement/ · fetched 2026-09-28. "Kafka Queues (Share Groups) is now production-ready"; share groups let "multiple consumers ... concurrently process records from the same partitions with individual acknowledgment and delivery counting" (step 10). Valid as of 2026-09-28.
- Apache Kafka, KIP-932: Queues for Kafka: https://cwiki.apache.org/confluence/display/KAFKA/KIP-932%3A+Queues+for+Kafka · fetched 2026-09-28. "The number of consumers in a share group can exceed the number of partitions in a topic"; "Records are acknowledged on an individual basis, although the system is optimized to work in batches for improved efficiency"; "Delivery attempts to consumers in a share group are counted to enable automated handling of unprocessable records" (steps 10–11). Cited model, not run. It corrects the older claim that Kafka has no per-message retries: that holds only for classic groups. Valid as of 2026-09-28.
- RabbitMQ docs, Queues and Consumer Acknowledgements: https://www.rabbitmq.com/docs/queues and https://www.rabbitmq.com/docs/confirms · fetched 2026-09-28. "A queue in RabbitMQ is an ordered collection of messages"; positive acks "record a message as delivered and can be discarded"; "any delivery (message) that was not acked is automatically requeued when the channel (or connection) on which the delivery happened is closed"; "any redelivery can change order" (steps 3, 4, 12). Valid as of 2026-09-28.
- Amazon SQS Developer Guide, Visibility timeout: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html · fetched 2026-09-28. "If you don't delete it before the timeout expires, the message becomes visible again in the queue and can be retrieved by another consumer"; "due to the at-least-once delivery model of Amazon SQS, there's no absolute guarantee that a message won't be delivered more than once" (steps 3–4). Valid as of 2026-09-28.
Related lessons
Checked against the tools and docs as of 2026-09-28.