jonnifyredis

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

2
deliveries deliver 1 effect deliver 2 effect deliver 3 effect 2 deliveries → 1 effect the NX marker absorbs the redeliveries · effect runs once

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.

GuaranteeWhen the job leavesFailure outcome
At-most-oncebefore the work (RPOP)a crash loses the job — zero deliveries
At-least-onceafter 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.

the pattern

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.

in EchoMQ

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.

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

Related in this course