R3.01.1 · dive 1
List as wait + active
A queue is a list used one way: push at one end, pop at the other. A reliable queue is two such lists — and a job that moves between them.
Everything in a reliable queue rests on the Redis LIST. Push at the head with
LPUSH, pop at the tail with RPOP, and the order out matches
the order in — FIFO, with no extra structure. A reliable queue names two such lists, a waiting list and an
in-flight list, and a job lives in exactly one of them; its position is its state. Push jobs into the waiting
list on the right and pop them in FIFO order — the readout shows the contents, head, and tail.
LPUSH head, RPOP tail · FIFO over jobs 5, 6, 7
A list is a queue
A Redis LIST is an ordered sequence of strings with cheap push and pop at both ends. Treat one end as the head
and the other as the tail, push at the head and pop at the tail, and the list becomes a first-in-first-out
queue. LPUSH key value prepends to the head; RPOP key removes
from the tail. A job pushed first is popped first.
LPUSH work 5 # head: [5] LPUSH work 6 # head: [6, 5] LPUSH work 7 # head: [7, 6, 5] RPOP work # -> 5 (the oldest, popped from the tail) RPOP work # -> 6
The list holds the order for free. The producer pushes at the head; the consumer pops at the tail; the list keeps the sequence between them. A queue is not a separate data structure — it is a list used with a push at one end and a pop at the other.
A queue is a list used one way: push at the head, pop at the tail, and the order out matches the order in — FIFO, with no extra structure.
wait and active are two named lists
A reliable queue does not use one list — it uses two. The first is the waiting list: jobs a producer has enqueued and no worker has yet taken. The second is the active (in-flight) list: jobs a worker has taken and is processing. A job lives in exactly one of them at a time, and its position names its state. A job in the waiting list is waiting; a job in the active list is being worked.
The two lists are named keys. EchoMQ names them with a queue prefix:
emq:{queue}:wait is the waiting list and emq:{queue}:active
is the in-flight list, where {queue} stands in for the queue name. The job does not
change — it is the same string id — but which list holds it changes, and that change is the state
transition. Reading a job's state is asking which list it is in.
emq:{queue}:wait (LIST) # jobs enqueued, not yet taken emq:{queue}:active (LIST) # jobs taken by a worker, in flight
- waiting list
- The list a producer enqueues into — EchoMQ's
emq:{queue}:wait. A job here is waiting for a worker. - in-flight list
- The list a worker moves a job into for processing — EchoMQ's
emq:{queue}:active. A job here is being worked, and a crash leaves it here, recoverable. - state = location
- A job's state is which list holds it. The transition is the move between the two lists.
A reliable queue is two named lists, a waiting one and an in-flight one; a job lives in exactly one, and its location is its state.
The move between the two lists
Taking a job for work means moving it out of the waiting list and into the active list. Doing that as two
separate commands — pop from wait, then push to active
— opens a gap: between the two, the job is in neither list, and a crash there drops it. The next dive is
exactly that gap and the single command that closes it. For now, hold the shape: two named lists, and a job that
belongs to whichever one currently holds it.
A per-worker variant gives each worker its own in-flight list (processing:worker1,
processing:worker2), so a recovery monitor can attribute a parked job to the worker
that holds it. The lists are still LISTs, and the move is still a move between two of them — only the
destination is now worker-specific. The third dive builds on that.
A job's state is its list · job 7 in wait, then in active
The move that takes a job for work is a move between two lists; doing it in two commands opens a gap, and closing that gap is the next dive.
In EchoMQ — the two named lists
EchoMQ names the two lists in real code, in echo/apps/echomq.
EchoMQ.Keys.wait/1 builds "#{base(ctx)}:wait" →
emq:{queue}:wait, and EchoMQ.Keys.active/1 builds
"#{base(ctx)}:active" → emq:{queue}:active. A new job is
pushed to the waiting list; a worker that takes a job moves it into the active list. The move itself is
moveToActive-11.lua's rcall("RPOPLPUSH", waitKey, activeKey)
— the job leaves the wait LIST and lands in the active
LIST as one command.
-- moveToActive-11.lua — the two lists, named (real) local waitKey = KEYS[1] -- emq:{queue}:wait (EchoMQ.Keys.wait/1) local activeKey = KEYS[2] -- emq:{queue}:active (EchoMQ.Keys.active/1) local jobId = rcall("RPOPLPUSH", waitKey, activeKey) -- wait -> active
{queue} is a documentation placeholder for the queue name — the real builder is
EchoMQ.Keys.base/1 ("#{prefix}:#{name}"), so the queue
my_queue yields emq:my_queue:wait. It is not the cluster
hash-tag {tag} that forces slot colocation; it is the queue name standing in for
readability.
A reliable queue is two named LISTs — a waiting list and an in-flight list — and a job whose state is which of them holds it.
EchoMQ.Keys.wait/1 builds emq:{queue}:wait and
EchoMQ.Keys.active/1 builds emq:{queue}:active; the move
between them is moveToActive-11.lua's RPOPLPUSH.
A reliable queue is two named lists and a move between them; EchoMQ's
wait/1 and active/1 keys are exactly those two lists.
wait and active — is the dedicated
EchoMQ course. From here, this dive opens onto E2 · the engine
— the as-built library and its full set of keys. This dive teaches the two-list primitive; that course
teaches the whole layout.
References
The Redis list type and its two push/pop ends, and the queue layout EchoMQ ports.
Sources
- Redis — Lists — the list type and its push/pop ends, the list-as-queue primitive everything rests on.
- Redis — LPUSH — prepend a job to the head of a list.
- Redis — RPOP — remove and return a job from the tail of a list.
- BullMQ — the queue protocol — the wait/active list layout EchoMQ ports.
Related in this course
- R3.01 · Processing list — the module hub: the in-flight move.
- R3.01.2 · LMOVE / RPOPLPUSH — the single command that moves a job between the two lists without a gap.
- R3.01.3 · The in-flight list —
activeread as the recovery ledger. - R3 · The reliable queue — the three guarantees in one place.
- E2 · the engine — the dedicated EchoMQ course: the worker fetch loop in depth.