R3 · Reliable Queues
At-least-once
Guarantee at-least-once message delivery using LMOVE to atomically transfer messages to a processing list, enabling recovery if consumers crash before completing work.
R3.01 built the move that loses no job: a worker moves a job into an in-flight list, so a crash leaves it parked and recoverable. That move makes redelivery possible — and that is the point. The consequence is precise: a reliable queue delivers a job one or more times, never zero. A finish-then-crash before the acknowledgement makes a duplicate inevitable, so the responsibility for absorbing it moves to the consumer, which must be idempotent. This module reads that bargain.
Delivered N times, effect once
A reliable queue can hand the same job to a worker more than once. What separates a correct consumer from a
broken one is whether the effect stays at one no matter how many times the job arrives. The toggle below
replays one job up to three times against two consumers: an idempotent one that claims a dedup marker with
SET … NX before the effect, and a non-idempotent one with no marker. The readout
reports deliveries and effects side by side.
Replay one job N times · the de:{id} marker · how many effects
Delivered N times, effect once. A reliable queue is at-least-once; an idempotent consumer is what turns that into an exactly-once effect.
Recovery from Failures
A separate monitor process — a reaper — periodically scans the processing list for stalled messages. For each job that has sat there longer than a timeout, it treats the worker as dead and moves the job back to the main queue with a second atomic move. R3.01 grounded this in EchoMQ's stalled sweep. The recovery path is what makes the queue reliable, and it is also what makes redelivery inevitable.
The window is precise. A worker takes a job into the in-flight list under a lock, applies the effect — charges a card, writes a row, sends a mail — and then crashes before it can remove the job from the in-flight list. The effect already happened; the acknowledgement did not. The reaper finds a job whose lock expired, returns it to the main queue, and a second worker runs it again. The effect runs twice.
This guarantees “at-least-once” delivery — messages may be processed multiple times but are never lost.
That sentence from the source is the whole bargain. The dependency chain is exact: R3.01's in-flight move makes redelivery possible (that is the design); a finish-then-crash before the acknowledgement makes it inevitable; therefore the consumer must be idempotent. There is no fourth option that removes the duplicate without reintroducing the loss.
The recovery path that never loses a job is the same path that can redeliver one. Keep the recovery, accept the duplicate, and move the work to the consumer.
Delivery Guarantees
This pattern provides at-least-once delivery, which is two properties stated together:
Messages are never lost. A job always exists in some list — the main queue or the in-flight list — so no crash drops it.
Messages may be processed multiple times. A worker that crashes after the effect but before the acknowledgement leaves the job in-flight, and recovery runs it again.
The three guarantees that bound the design space are worth naming together. At-most-once removes the job before the work, so a crash loses it — zero or one delivery, never a duplicate, but a job can vanish. At-least-once keeps the job until the acknowledgement, so a crash redelivers it — one or more deliveries, never zero, but a job can repeat. Exactly-once would be one delivery and exactly one — and it is not achievable as a delivery guarantee, because the acknowledgement itself can be lost, which is the subject of the third dive.
| Guarantee | When the job leaves | Failure outcome |
|---|---|---|
| At-most-once | before the work (RPOP) | a crash loses the job — zero deliveries |
| At-least-once | after the acknowledgement (LMOVE + LREM) | a crash redelivers — one or more deliveries |
| Exactly-once | — | not a deliverable guarantee — build it as an idempotent effect |
For exactly-once semantics, make message handlers idempotent — processing the same message twice should produce the same result as processing it once.
This is where the work moves to the consumer. Idempotency converts at-least-once delivery into an
exactly-once effect. Some effects are naturally idempotent — a SET key value
overwrites to the same value however many times it runs. Others are not — an INCR,
a charge, a row insert — and those need a guard: a dedup marker claimed with SET … NX
keyed by a stable job id, so the second delivery finds the marker and skips.
A reliable queue must keep the job until it is acknowledged, so it is at-least-once — the only point on the spectrum that loses no job.
In EchoMQ — producer dedup, not consumer exactly-once
EchoMQ carries a real deduplication key, and it is worth being precise about what it does.
EchoMQ.Keys.dedup/2 builds emq:{queue}:de:{id}
("#{base(ctx)}:de:#{dedup_id}"). A producer enqueuing a job with a deduplication id
claims this marker; a second enqueue carrying the same id is dropped because the marker is already present. The
Lua script removeDeduplicationKey-1.lua cleans it up: it GETs
the key and DELs it only when the stored value matches the job id, so the cleanup never
deletes a marker some other job holds.
# at-least-once delivery + an idempotent effect = exactly-once effect (the consumer's job) if SET dedup:{job_id} 1 NX: # claim the marker; NX = only if absent apply_effect(job) # first delivery: run the effect once else: skip() # a redelivery: the marker is already set, do nothing
-- EchoMQ.Keys.dedup/2 — producer-side dedup key (real) def dedup(ctx, dedup_id), do: "#{base(ctx)}:de:#{dedup_id}" # emq:my_queue:de:<id>
This is producer-side deduplication. It stops two identical jobs from entering the queue at
enqueue time. It is not consumer exactly-once: it says nothing about a single job being
delivered twice to a worker, because the in-flight recovery path can still redeliver a job that entered
the queue once. Producer dedup and consumer idempotency are two different keys protecting two different moments
— the enqueue and the effect. The exactly-once effect a reliable queue ships is the consumer's
job: a naturally idempotent effect, or a marker claimed at effect-time with SET … NX.
A reliable queue is at-least-once; the consumer must be idempotent. The exactly-once effect is the consumer's to build — a naturally idempotent effect, or a marker claimed at effect-time.
The producer-side EchoMQ.Keys.dedup/2 marker
(emq:{queue}:de:{id}, cleaned by removeDeduplicationKey-1.lua)
dedups at enqueue, while the consumer's effect must still be made idempotent to absorb a redelivery the
recovery path cannot avoid.
EchoMQ's de:{id} marker stops duplicate submissions; an idempotent
consumer absorbs duplicate deliveries. Two keys, two moments.
A door, not a depth
This module cites one excerpt of EchoMQ's protocol — the producer dedup key — as proof the pattern ships. The worker fetch loop that delivers a job is E2 · the EchoMQ core, and the acknowledge/finish lifecycle that closes a job out is E6 · the lifecycle — both in the dedicated EchoMQ course, built next with this toolkit. This module teaches the at-least-once bargain; that course teaches the engine that runs it.
The three dives
Each dive takes one face of the bargain, in order: the three guarantees and why the move-under-lock yields at-least-once; making the consumer idempotent so a redelivery changes nothing; and the acknowledgement-gap argument that proves exactly-once delivery cannot exist. Read them in order — the semantics first, the idempotent consumer second, the impossibility proof third.
At-least-once semantics
The three delivery guarantees, why the move-under-lock yields at-least-once, and the finish-then-crash-before-ack window. Never lost; possibly twice.
Idempotent consumers
A naturally idempotent SET versus a dedup marker for a non-idempotent
INCR or charge. Worked example: a Portal enrollment.
Why exactly-once is a lie
The acknowledgement-gap argument: exactly-once delivery is impossible, exactly-once
effect is what you build. de:{id} is producer dedup.
After R3.02, the Reliable Queues chapter continues with the processing list, stalled recovery, and a workshop. Return to the Reliable Queues chapter to continue.
References
The Redis mechanisms behind at-least-once delivery and the idempotency marker, and the queue protocol whose producer-side dedup key grounds this module.
Sources
- Redis — SET — the
NXoption behind the idempotency marker (SET … NX): claim the key only if absent. - Redis — RPOPLPUSH — the at-least-once move into the in-flight list that makes redelivery possible.
- Redis — Redis queue — the queue and its delivery-guarantee overview.
- BullMQ — the queue protocol — the worker path EchoMQ ports, including custom job ids and deduplication.
Related in this course
- R3.02.1 · At-least-once semantics — the three guarantees and the duplicate window.
- R3.02.2 · Idempotent consumers — the marker and the naturally idempotent effect.
- R3.02.3 · Why exactly-once is a lie — the acknowledgement-gap argument.
- R3 · The reliable queue — the family in one place.
- R3 · Reliable Queues — the chapter.
- R2.01 · Atomic updates — the atomic move the move-under-lock rests on.