R3.04.1 · dive 1
States as locations
The value the lifecycle updates is not a status field. It is the Redis key the job id lives in — and a transition moves the id to the next key.
A naive queue stores a status column and updates it as the job moves,
so the location and the status can disagree after a crash. EchoMQ holds no status field:
emq:{queue}:wait and :active are LISTs,
:delayed, :prioritized, :completed and
:failed are ZSETs, and the state is the key the id sits in. Fire a transition
on the right and watch the id hop from one key to the next.
States as locations · the job id hops from one key to the next
The state is the location
The atomic-updates pattern names the thing being updated as a value at a key. Applied to a queue, that value is
the job's place. A naive queue stores a status field on the job record —
"wait", "active", "done" — and
updates it as the job moves. Two facts can then disagree: where the job actually sits, and what its status
claims. A crash between writing the status and moving the job leaves the pair out of sync, with nothing to
reconcile them.
EchoMQ holds no status field. A job's state is the Redis key its id lives in. There is no separate column to fall out of sync, because the location is the state.
A job's state is not a field that can lie about where the job is — the state is the key the id lives in, so the location can never disagree with itself.
The five locations
A job id lives in exactly one place at a time, and the structure type carries meaning. The wait and active states are LISTs; delayed, prioritized, completed and failed are sorted sets scored by time or priority:
# a job's state is the key its id lives in — no status column emq:{queue}:wait → LIST · ids waiting to run emq:{queue}:active → LIST · ids in flight under a lock emq:{queue}:delayed → ZSET · scored by when the job is due emq:{queue}:prioritized → ZSET · scored by priority emq:{queue}:completed → ZSET · finished ids, scored by finish time emq:{queue}:failed → ZSET · failed ids
EchoMQ.Keys builds each from the queue context — wait/1,
active/1, delayed/1, prioritized/1,
completed/1, failed/1. The {queue} here
is the queue-name placeholder the key builder fills in (EchoMQ.Keys.base/1), the
documentation form the shipped queue dives use.
- wait / active
- LISTs. A job is pushed onto wait and moved into active to run; the active list is the in-flight set under a lock.
- delayed / prioritized
- ZSETs. The score is the due time (delayed) or the priority (prioritized), so the queue can pop the next-due or highest-priority id.
- completed / failed
- ZSETs of terminal ids, scored by finish time, so old entries can be trimmed by age or count.
LIST membership means waiting or in flight; ZSET membership means scheduled or terminal — the structure type is part of what the state means.
A transition moves the id
A transition is moving the id from one key to the next, plus an update to the job hash
emq:{queue}:<id>. Enqueue pushes the id onto wait. Pick up
moves it from wait into active. Finish moves it from
active into completed. Fail moves it from
active into failed. The id is in exactly one structure at
every instant — and which structure it is in is the job's state.
Because the state is the location and a transition is a move across structures, the move must be atomic. A move
that removes the id from one key and adds it to another in two separate commands has a window where the id is in
neither, or in both. That is why the whole transition runs as one Lua script —
moveToFinished-14.lua, one EVALSHA over the fourteen keys that
name every location at once. Pick a transition below and watch which structure the id leaves and which it
enters.
One transition · which structure the id leaves, which it enters
The value being updated is a location, not a status field, so the location can never disagree with the state. A transition is an atomic move of the id from one structure to the next.
The locations are EchoMQ.Keys.wait/active/delayed/prioritized/completed/failed, the
job hash is EchoMQ.Keys.job/2, and a transition moves the id between them in one
moveToFinished-14.lua step.
A transition removes the id from one structure and adds it to another in one atomic move, so the id is in exactly one location at every instant.
In EchoMQ — the location is the state
EchoMQ's queue holds no status column. The EchoMQ.Keys module builds every location
from the queue context, and a job's state is read off which key its id sits in. A transition is an atomic move
across structures — the same moveToFinished-14.lua that the next dive opens. No
status field means there is no second fact to fall out of sync with where the job actually is.
References
The Redis structures a job id lives in — the lists, the sorted sets, the job hash — and the queue protocol whose location model EchoMQ ports.
Sources
- Redis — Lists — the
waitandactivestructures a job id is pushed onto and moved between. - Redis — Sorted sets — the scored
delayed,prioritized,completedandfailedlocations. - Redis — Hashes — the job hash
emq:{queue}:<id>updated alongside the move. - BullMQ — the queue protocol — the wait/active/completed/failed location model EchoMQ ports.
Related in this course
- R3.04 · Atomic state machine — the module: the transition as one EVALSHA.
- R3.04.2 · Read-decide-write in one EVALSHA — the next dive: the move across these locations as one call.
- R3 · States as locations — the standalone orientation dive on the same framing.
- R3.01 · The reliable queue — the in-flight move that parks a job in
active. - R2.01 · Atomic updates — the read-modify-write the transition is built on.
- E6 · The job lifecycle — the dedicated EchoMQ course: the full key taxonomy and transition graph.