jonnifyredis

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

stage: enqueued — the job is in wait, a worker is blocked on the marker emq:{queue}:wait enroll j_1 emq:{queue}:active · lock enroll j_1 emq:{queue}:completed enroll j_1 action: ZADD emq:{queue}:marker 0 "0" — wake the worker queued — press step to pick it up enrolled (effect) 0 job runs so far: 0 — the effect count is what matters, not the delivery count effect: nothing yet — the loop has not picked up the job
Start: the enroll job j_1 is in wait, a worker is blocked on the marker. Press “step the job” to run the reliable loop, or “crash mid-process” to inject a worker death.

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.

PatternThe move over the enroll jobThe failure it prevents
R3.01 · processing listmoveToActive-11: RPOPLPUSH wait active under a locka lost job — a crash leaves it recoverable in active
R3.02 · at-least-oncefinish-then-crash redelivers; delivery is at-least-oncea dropped enrollment — the job runs again, not zero times
R3.03 · stalled recoverymoveStalledJobsToWait-8: expired lock → back to waita stuck job — a dead worker’s job returns for another
R3.04 · atomic finishmoveToFinished-14: one EVALSHA, lock + result + move + nexta corrupt half-state — no torn finish
R3.05 · blocking pickupBZPOPMIN on emq:{queue}:marker; a job add ZADDs ita 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.

ActionWhat the loop doesEnrolled count
step the jobadvance one stage: wake → pick up → enroll → finish → completed0 → 1 on the enroll, then stable
crash mid-processworker dies after the enroll, before the finish; lock expires; sweep returns the job to wait; a second worker re-runs itstays 1 — the redelivery is a no-op
resetrestore the start state — the job back in wait, the count back to zero0

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

Portal.enroll/2 over the same learner and course — the effect runs once deliveries (at-least-once) 0 enrolled (effect) 0 press run — the first delivery records the enrollment
Start: zero deliveries, zero enrolled. Press “run the enroll job” repeatedly — deliveries climb, but the enrolled count stays one after the first run.
the chapter, assembled

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 Portal application

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.

GuaranteeThe EchoMQ symbol (Elixir, verified)The key
in-flight moveEchoMQ.Scripts.move_to_active/4 → moveToActive-11.luaemq:{queue}:wait → :active
stalled recoveryEchoMQ.Scripts.move_stalled_jobs_to_wait/4 → moveStalledJobsToWait-8.luaemq:{queue}:stalled
atomic finishEchoMQ.Scripts.move_to_finished/7 → moveToFinished-14.luaemq:{queue}:active → :completed
blocking pickupEchoMQ.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

Related in this course