R3 · Reliable Queues
A reliable enrollment queue
Assemble the five reliable-queue patterns into one worked job — an enrollment that runs at-least-once and, because the consumer is idempotent, lands exactly once.
The chapter taught five patterns: the processing list, at-least-once delivery, stalled recovery,
the atomic state machine, and the blocking pickup. This capstone is the one worked design that needs all five at
once — a reliable queue for a single, concrete job: enrolling a learner in a Portal course.
An enroll request is enqueued, a worker processes it, the job is completed once. A worker crash never drops the
enrollment and never leaves a corrupt half-state. The queue is at-least-once, so the job can run twice — and
that is safe because Portal’s enroll/2 is already idempotent. This is an
applied design: Portal does not run on EchoMQ today.
The enroll job · step it through the reliable loop, with or without a mid-process crash, and read the enroll count
One job, five guarantees, one count. The enroll runs at-least-once — possibly twice after a crash — and the enrolled count stays one, because the effect is idempotent.
The scenario — enrollment as a job
Portal enrolls a learner in a course. Frame that enrollment as a job: an enrollment request is enqueued, a worker processes it, and the job is completed once. “Reliable” means two things at the same time. A worker crash never drops an enrollment — the learner is enrolled, eventually, even if the first worker dies mid-process. And a worker crash never enrolls a learner into a corrupt half-state — no torn write, and no double-enrollment when the job runs twice.
Be plain about the boundary. Portal’s enroll is synchronous today and does not run
through EchoMQ. EchoMQ is the candidate for Portal’s reserved F7–F9
multi-runtime layer. This workshop is the applied design: the reliable-queue path R3 built, laid over
an enrollment job, so each pattern’s guarantee maps to a real failure it prevents. The Redis keys and Lua
scripts are EchoMQ’s real ones; the wiring of enroll-as-a-queue-job is the design this page assembles, not
Portal’s current architecture.
An enrollment is a job: enqueued, processed by a worker, completed once. Reliable means lose no enrollment and corrupt no state — even across a worker crash.
The assembled path — R3.01 through R3.05, in order
Five patterns assemble into one reliable loop. Each prevents a different failure of the naive form — pop a job, process it, done — and they build in order.
| Pattern | The move over the enroll job | The failure it prevents |
|---|---|---|
| R3.01 · processing list | moveToActive-11: RPOPLPUSH wait active under a lock | a lost job — a crash leaves it recoverable in active |
| R3.02 · at-least-once | finish-then-crash redelivers; delivery is at-least-once | a dropped enrollment — the job runs again, not zero times |
| R3.03 · stalled recovery | moveStalledJobsToWait-8: expired lock → back to wait | a stuck job — a dead worker’s job returns for another |
| R3.04 · atomic finish | moveToFinished-14: one EVALSHA, lock + result + move + next | a corrupt half-state — no torn finish |
| R3.05 · blocking pickup | BZPOPMIN on emq:{queue}:marker; a job add ZADDs it | a busy-poll — the worker sleeps until woken |
The processing list is the foundation: the enroll job moves from wait to
active in one atomic step, so it is in exactly one list at every instant and a crash
leaves it recoverable. That in-flight move is what makes redelivery possible — which is the point
— and it is also what makes delivery at-least-once: a worker that enrolls the learner,
then dies before it acknowledges, leaves the job in active to be redelivered. The
stalled sweep reclaims that job; the atomic finish keeps the completion indivisible; the blocking pickup keeps
the worker idle-cheap between jobs. Assembled, they are a queue that loses no job.
# The assembled path, over a Portal enroll job (applied design — Portal does not run on EchoMQ today) enqueue → ZADD emq:{queue}:marker 0 "0" # R3.05 — wake a blocked worker worker → BZPOPMIN emq:{queue}:marker # R3.05 — block, no polling pick up → moveToActive-11 RPOPLPUSH wait active # R3.01 — in-flight, take the lock process → Portal.enroll/2 → %LearnerEnrolled{} # the effect on redelivery → {:error, :already_enrolled} # idempotent — a no-op finish → moveToFinished-14 (one EVALSHA) # R3.04 — drop lock, record, active→completed, next crash mid-job? → lock expires → moveStalledJobsToWait-8 # R3.02 + R3.03 — back to wait, retry
Each pattern guards one failure: a lost job, a dropped enrollment, a stuck job, a torn finish, a busy-poll. Assembled in order, they are a queue that never loses an enrollment.
Run the job — and the crash
The interactive in the hero steps a single enroll job through the assembled loop over a fixed start state. Each press of step the job advances one stage of the reliable loop — wake, pick up under the lock, enroll, finish — and the readout shows the job’s list location and the enrolled count at each step. The function behind it is pure: it returns a new state and stores nothing.
Press crash mid-process at the enroll stage to inject the failure the chapter was built to
survive. The worker dies after recording the enrollment but before the atomic finish. The job is stranded in
active; its lock expires; the stalled sweep returns it to wait;
a second worker picks it up and runs the enroll again. Because the enroll is idempotent, the second run returns
{:error, :already_enrolled} and records nothing — and the readout shows
enrolled: 1 even though the job ran twice.
| Action | What the loop does | Enrolled count |
|---|---|---|
step the job | advance one stage: wake → pick up → enroll → finish → completed | 0 → 1 on the enroll, then stable |
crash mid-process | worker dies after the enroll, before the finish; lock expires; sweep returns the job to wait; a second worker re-runs it | stays 1 — the redelivery is a no-op |
reset | restore the start state — the job back in wait, the count back to zero | 0 |
Two behaviours are worth watching directly. The crash loses nothing — the job that died
in active comes back through the sweep and finishes on the second worker.
The redelivery corrupts nothing — the re-run hits the idempotency guard, so the enrolled
count is one, not two. Both are the reliability and the idempotency the five R3 patterns were building toward,
in one loop.
Step the job and it completes once; crash it mid-process and it still completes once. At-least-once delivery over an idempotent effect is exactly-once-in-effect — visible in the count.
The synthesis — at-least-once + idempotent enroll
The assembled queue guarantees at-least-once delivery, so the enroll job can run twice. That is safe, because
Portal’s enroll is already idempotent. Portal.Engine.Core.authorize/2
checks the folded enrollment state before any event is recorded:
# Portal.Engine.Core.authorize/2 — the real guard (echo/apps/portal/lib/portal/engine/core.ex) if MapSet.member?(enrolled, course_id), do: {:error, :already_enrolled}, else: :ok
A first enroll appends %Portal.Enrollment.Events.LearnerEnrolled{user_id, course_id, at}
and the learner is enrolled. A redelivered enroll of the same learner into the same course hits the guard,
returns {:error, :already_enrolled} — a real, closed
Portal.Error code — and records nothing. Enrolling twice equals enrolled once.
The interactive below makes the proof concrete. Run the same enroll job any number of times; the enrolled count
stays at one. The first run records the event; every later run returns {:error,
:already_enrolled} before any write. At-least-once delivery over an idempotent effect is
exactly-once-in-effect.
The idempotency proof · run the same enroll job N times and read the enrolled count
A reliable queue — the in-flight move (R3.01), at-least-once delivery (R3.02), stalled recovery (R3.03), the atomic finish (R3.04), the blocking pickup (R3.05) — loses no job, but delivers each one or more times.
The consumer is Portal.enroll/2, already idempotent via
Portal.Engine.Core.authorize/2 returning {:error,
:already_enrolled} — so a redelivered enroll is a no-op, and the assembled queue is
exactly-once-in-effect.
A reliable queue plus an idempotent consumer is the whole reliable design: lose no work, corrupt no state. Exactly-once delivery is a lie; exactly-once-in-effect is the achievable truth.
Grounded in EchoMQ’s iconic Elixir move and Portal’s real enroll
Every Redis primitive in the assembled path is real EchoMQ code in the iconic Elixir app
echo/apps/echomq — the same symbols R3.01–R3.05 each cite.
EchoMQ.Scripts.move_to_active/4 runs moveToActive-11.lua;
EchoMQ.Scripts.move_stalled_jobs_to_wait/4 runs
moveStalledJobsToWait-8.lua; EchoMQ.Scripts.move_to_finished/7
runs moveToFinished-14.lua (fourteen keys, fetch_next default
one). The worker blocks on EchoMQ.Keys.marker/1 —
emq:{queue}:marker, a ZSET — via BZPOPMIN
(EchoMQ.Worker.wait_for_job/2), and a job add wakes it with
ZADD marker 0 "0".
EchoMQ’s governing rule is “the Lua scripts ARE the protocol”: the same scripts are meant to run identically across the Elixir, Go, and Node.js runtimes. The honest status is that the three are not at parity — the iconic Elixir runtime is the most complete — so a guarantee holds wherever the script runs as one atomic step, strongest in Elixir.
| Guarantee | The EchoMQ symbol (Elixir, verified) | The key |
|---|---|---|
| in-flight move | EchoMQ.Scripts.move_to_active/4 → moveToActive-11.lua | emq:{queue}:wait → :active |
| stalled recovery | EchoMQ.Scripts.move_stalled_jobs_to_wait/4 → moveStalledJobsToWait-8.lua | emq:{queue}:stalled |
| atomic finish | EchoMQ.Scripts.move_to_finished/7 → moveToFinished-14.lua | emq:{queue}:active → :completed |
| blocking pickup | EchoMQ.Worker.wait_for_job/2 (BZPOPMIN) | emq:{queue}:marker |
The consumer is real Portal code in echo/apps/portal. The command is
Portal.enroll/2 — the Portal facade function that
delegates to Portal.Enrollment.enroll/2. The recorded fact is
%Portal.Enrollment.Events.LearnerEnrolled{user_id, course_id, at}, emitted by
Portal.Engine.Core.decide/2 and folded by evolve/2. The
idempotency guard is Portal.Engine.Core.authorize/2, which returns
{:error, :already_enrolled} against the folded state — the closed
Portal.Error code that makes a redelivered enroll a safe no-op.
The wiring here — an enroll request dispatched as an EchoMQ queue job — is the applied
design, not Portal’s current architecture. Portal’s enroll is synchronous today; EchoMQ is the
candidate for the reserved F7–F9 multi-runtime layer. The keys and scripts are real;
the queue-over-enrollment is the design this workshop assembles.
The reliable-queue path and Portal’s idempotent enroll are one shape: a queue that loses no job plus a consumer that corrupts no state — assembled, exactly-once-in-effect.
A door, not a depth
You have built the reliable-queue patterns; the engine that runs them — the full Lua inventory, the
heartbeat manager that renews the lock, the stalled-check coordination across a worker pool, and the three
runtime implementations (the iconic Elixir echo/apps/echomq, the Go runtime with its
documented gaps, the Node reference) — is the subject of the dedicated
EchoMQ course, the companion depth course that teaches the polyglot
protocol in full. This workshop closes R3 with one reliable enrollment job; that course teaches the engine
behind it. Return to the Reliable Queues chapter to continue.
References
The Redis primitives the reliable enrollment queue assembles — the atomic in-flight move, the cached finish script, the blocking pickup, and the lock lease — and the queue protocol whose worker path EchoMQ ports.
Sources
- Redis — RPOPLPUSH — the atomic in-flight move (wait → active), the heart of
moveToActive-11.lua. - Redis — EVALSHA — the atomic finish:
moveToFinished-14.luaruns as one cached script. - Redis — BZPOPMIN — the worker blocks on the marker between jobs; no polling loop.
- Redis — SET —
SET NX PX, the job lock lease whose expiry signals a dead worker. - Redis — Redis queue — the reliable-queue model this workshop assembles.
- BullMQ — the queue protocol — the reliable worker path EchoMQ ports, where the Lua scripts are the protocol.
Related in this course
- R3 · Reliable Queues — the chapter this workshop closes.
- R3.01 · The processing list — the in-flight move (wait → active under a lock).
- R3.02 · At-least-once — the delivery guarantee the idempotent consumer pays for.
- R3.03 · Stalled recovery — the sweep that returns a dead worker’s job to wait.
- R3.04 · The atomic state machine — the one-script finish.
- R3.05 · Blocking vs polling — the blocking pickup on the marker.
- EchoMQ · The core — the door: the engine that runs these scripts across three runtimes.