jonnify·redis /redis-patterns/queues/processing-list/the-in-flight-list

R3.01.3 · dive 3

The in-flight list.

The in-flight list is not a buffer jobs pass through. It is a ledger: at any instant it holds exactly the jobs being processed right now.

A job moved into active stays there for the whole duration of the work, and only an explicit acknowledgement removes it. So the list is a live record of in-flight work — and a worker that dies mid-job leaves its job sitting in the record, found and returnable. EchoMQ scores each member at its lease deadline, so the score names when a job is due back; the ack runs only when the work is truly done. Select a worker dead and read where its job stays.

The recovery ledger — a dead worker leaves its job in active. Select the worker's state to read where job 7 stays.
worker 1alive · renewing lease active set (ledger)job 7 reap returns job 7 → pending worker 1 alive · job 7 in the ledger, lease renewed
§1

The list is a record, not a buffer

reliable-queue.md.txt · the ledger

A job moved into active does not pass through — it stays there for the entire duration of the work, and only an explicit acknowledgement removes it. So active is a live record: every job in it is a job a worker has taken and not yet finished. Reading the list answers a question that matters for reliability — what work is in flight right now — without any extra bookkeeping.

emq:{queue}:active  →  [ job 7, job 6 ]   # two jobs in flight, each scored at its lease deadline

This is what makes a crash recoverable. When a worker dies between taking a job and acknowledging it, the job is still in active. Nothing was lost, because nothing removed it. A recovery process reads the set and finds the parked job, exactly because the set is a record of in-flight work and not a transient buffer.

The in-flight list is a live record of work in progress; a crashed worker leaves its job in the record, recoverable, because only an explicit ack removes it.

§2

Per-worker lists, or a lease score

echo_mq/jobs.ex · reap/2

One shared in-flight list works, but it cannot tell which worker holds which job. The classic LIST form gives each worker its own in-flight list: processing:worker1, processing:worker2. A job in processing:worker1 is held by worker 1. When worker 1 dies, a recovery monitor reads worker 1's list, finds the parked jobs, and returns them to the waiting list for another worker — LMOVE processing:worker1 work_queue RIGHT RIGHT. Per-worker lists make the attribution direct: the list name is the worker.

A real engine refines the death signal beyond "how long has the job sat here." EchoMQ scores each member of emq:{queue}:active at its lease deadline on the server clock; a member whose score has passed now is a worker that stopped renewing, and EchoMQ.Jobs.reap/2 returns it to pending. The lease score is the death signal, so EchoMQ needs no per-worker list — the ledger's own score names the deadline. R3.03 is the count-thresholded sweep on top.

per-worker list
Each worker has its own in-flight list (processing:workerN), so a recovery monitor attributes a parked job to the worker that holds it.
lease score
EchoMQ scores each active member at its due-back time; a score past now is the death signal, no per-worker list needed.
recovery ledger
The in-flight set read as a record: exactly the jobs in flight, each with its deadline, found and returnable after a crash.

Name when each parked job is due back — a per-worker list, or a lease score — so a recovery sweep returns it; EchoMQ uses the lease score as the death signal.

§3

The ack removes it only when done

reliable-queue.md.txt · complete/4

A job leaves the in-flight list with one command: LREM active 1 job — remove one occurrence of this job from the list. The discipline is when it runs: only after the work is truly complete. Run it too early — before the side effect is durable — and a crash after the ack but before the effect loses the job for real, because the ledger no longer holds it. Run it after the effect, and a crash before the ack leaves the job parked, recoverable, and redelivered.

job = RPOPLPUSH wait active   # take the job into the in-flight ledger
process(job)                  # do the work; durable side effect lands here
LREM active 1 job             # ack: remove from the ledger ONLY now

That ordering is what makes delivery at-least-once rather than at-most-once. A job is delivered again if the worker died before the ack, and delivered once if the ack landed — never zero times. The cost is that a worker which finished the work and died before the ack redelivers the job, which is why R3.02 makes the consumer idempotent. This dive's job is the ledger and the ack; the redelivery cost is the next module's. Slide the crash point and read where the ack ordering protects the job.

Where the crash lands — ack-after-done protects the job. Select the crash point to light the step and read the verdict.
RPOPLPUSHinto active processdurable effect LREM ackafter done crash during work · job 7 still in active → recoverable

The ack is one LREM, and its timing is the rule: remove the job only after the work is durable, which is what makes delivery at-least-once.

§4

In EchoMQ — the in-flight ledger under a lease

echo_mq/jobs.ex · stalled.ex

EchoMQ's in-flight list is the emq:{queue}:active sorted set, built by EchoMQ.Keyspace.queue_key(queue, "active"), real in echo/apps/echo_mq. Each member is scored at its lease deadline on the server clock, so the score is both the in-flight marker and the recovery clock. The ack is EchoMQ.Jobs.complete/4: it is token-fenced on the row's attempts value, so only the worker that holds the current lease can retire the job — a worker whose lease was reaped and re-claimed by another worker is refused, and a redelivered completion is a no-op. A crash leaves the id in active, and EchoMQ.Jobs.reap/2 returns any member past its lease score to pending.

-- the ack and the recovery clock (echo/apps/echo_mq/lib/echo_mq/jobs.ex)
-- complete/4: token-fenced on attempts, only the lease holder may retire the row
local att = redis.call('HGET', KEYS[2], 'attempts')      -- KEYS[2] = the job row
if att ~= ARGV[2] then return redis.error_reply('EMQSTALE complete token mismatch') end
redis.call('ZREM', KEYS[1], ARGV[1])                       -- KEYS[1] = emq:{queue}:active, remove from the ledger
-- reap/2: any member whose lease score has passed now returns to pending

A job that stalls repeatedly past EchoMQ.Stalled's max_stalled threshold is not recovered forever: EchoMQ.Stalled.check/3 counts each stall on the row's stalled field and, at the threshold, dead-letters the job to emq:{queue}:dead. From the morgue the completion folds onward to the durable floor — the persistence frontier the echo-persistence course follows, where the bus archive lands in the page engine and replicates off-box. The lease score, the reaper, and the archive are the recovery machinery; this dive teaches the ledger and the ack.

Notes on ValkeyZADD key score member scores a member of a sorted set; EchoMQ scores each active member at its lease deadline, so a member whose score has passed now is the death signal the reaper reads — valkey.io/commands/zadd.

the pattern

The in-flight list is a recovery ledger: a parked job stays there until an explicit ack, so a crashed worker leaves it found and returnable, and the ack runs only when the work is done.

its EchoMQ application

emq:{queue}:active is the ledger, each member scored at its lease deadline; EchoMQ.Jobs.complete/4 is the token-fenced ack and reap/2 returns an expired lease, EchoMQ.Stalled dead-lettering a repeat-staller to the durable floor.

The in-flight list is a recovery ledger, not a buffer; EchoMQ's active set scored by lease, with the token-fenced complete/4 ack and the reaper, is exactly that ledger.

/echomq/queue

A door, not a depth

The reaper that returns an expired lease, the stalled sweep that counts repeated stalls, and the token-fenced ack across a worker pool are the dedicated EchoMQ course. From here, open onto the Queue pillar — the as-built leased state machine. This dive teaches the ledger and the ack.

/echo-persistence

Where a dead-lettered job lands

A job past its stall threshold is dead-lettered, and the completion folds onto the durability floor — the bus archive into the page engine and off-box replication. The durability dial is the subject of the persistence course.

§5

References

sources · related

Sources

  • Redis — Lists — the list as a record of ordered elements, the in-flight ledger read as a record of work.
  • Redis — LREM — the ack: remove one occurrence of a job from the in-flight list after the work is done.
  • Redis — RPOPLPUSH — the move that parks a job in the in-flight ledger atomically.
  • Valkey — ZADD — score a member of a sorted set, how EchoMQ records the lease deadline that doubles as the recovery clock.

Related in this course