R3.05.2 · dive 2
Blocking pop
A blocking pop parks the connection on the server until work arrives, removing the interval and the wasted round-trips at once.
The primitive is BLMOVE, BRPOPLPUSH, or
BZPOPMIN with a timeout: the call does not return until an element is available or the
timeout fires. The cost is the connection — while it is parked, a command on the same connection waits
behind the block. Set how long until a job arrives and the timeout on the right, and read what woke the block and
how many round-trips it used.
The blocking call · the connection state until an element arrives or the timeout
The primitive
A blocking command does not return immediately on an empty input. It parks the connection on the server until an element arrives or a timeout elapses. Three forms cover the reliable queue:
BLMOVE wait active RIGHT LEFT 30 # block until wait has an element, then move it; 30s timeout BRPOPLPUSH wait active 30 # the older blocking move (Redis < 6.2) BZPOPMIN marker 30 # block until the marker ZSET has a member, then pop the lowest
Each parks for up to the timeout. BLMOVE and BRPOPLPUSH block
on the work list itself and move an element when one arrives; BZPOPMIN blocks on a
sorted set and pops its lowest-scored member. EchoMQ uses the BZPOPMIN form on a marker
ZSET — the next dive — but the shape is the same: park, then wake on arrival.
- BLMOVE
- The modern blocking move (Redis 6.2+): block until the source list has an element, then move it to the destination, atomically.
- BRPOPLPUSH
- The older blocking reliable-queue pop, the form
BLMOVEsucceeds; same park-then-move shape. - BZPOPMIN
- Block on a sorted set until a member is added, then pop the lowest-scored member; the form EchoMQ parks on, over the marker ZSET.
- timeout
- An upper bound on the park, so the call returns even when no element arrives — not a poll interval.
A blocking pop parks the connection on the server until an element arrives or the timeout fires; the three forms differ only in what they park on — a work list or a marker ZSET.
What blocking removes
Against the polling loop, the blocking pop removes both costs the interval traded between. There is no poll interval, so a job is picked up the moment it arrives, not up to one interval later — pickup latency is effectively zero. There are no empty round-trips, because the call does not return until there is work or the timeout fires.
# polling: many round-trips, ~½-interval latency poll → nil → sleep → poll → nil → sleep → … → poll → job # blocking: one round-trip, ~0 latency park ─────────────────────────── job arrives → return
The timeout is not a poll interval: it is an upper bound on the park, so the worker can return to re-check its own liveness and is never blocked forever. A short timeout still costs nothing while idle — re-issuing a block is not a busy spin, because the time between blocks is spent parked, not spent sending empty commands.
Blocking removes the interval and the empty round-trips at once: a job is picked up the instant it arrives, in one round-trip, and the timeout only bounds the park.
The connection cost
The block has one price. While a connection is parked on BZPOPMIN, it is busy — a
command sent on the same connection waits behind the block until it returns. A worker that blocked on
its only connection could not fetch the job, renew a lock, or acknowledge a result while parked. Switch the
comparator below to one shared connection and send a command: it queues behind the block. Switch to a dedicated
blocking connection and the same command runs at once.
# one shared connection — the block starves the other commands conn: BZPOPMIN marker 30 # parked… conn: SET lock … # …waits behind the block, cannot run # a dedicated blocking connection — the main connection stays free block_conn: BZPOPMIN marker 30 # parks here main_conn: SET lock … # runs immediately on the free connection
Shared vs dedicated connection · can a command run while the worker is parked?
A blocking call ties up its connection for the whole park; a command on the same connection queues behind it. A dedicated blocking connection keeps the main one free.
In EchoMQ — the dedicated blocking connection
EchoMQ's worker parks on the dedicated connection by design.
EchoMQ.RedisConnection.blocking_connection/1 — documented as "a dedicated
blocking connection for operations like BRPOPLPUSH or BZPOPMIN" — hands the worker a connection used
only for the block. The blocking-fetch internals EchoMQ.Worker.wait_for_job/2 →
do_wait_for_job/3 run the BZPOPMIN emq:{queue}:marker timeout
on that connection, and do_wait_for_job/3 returns :job_available
when a marker arrived or :timeout when the park elapsed. The worker's main command
connection is untouched while it is parked, so it fetches the woken job and runs the rest of the lifecycle
without waiting behind the block.
# EchoMQ.Worker.do_wait_for_job/3 — park on the marker ZSET on the blocking connection (real) case Redix.command(conn, ["BZPOPMIN", marker_key, timeout_seconds], timeout: :infinity) do {:ok, nil} -> :timeout # the park elapsed, no job {:ok, [_key, _member, _sc]} -> :job_available # a marker arrived → a job is available end
Block on a primitive with a timeout to remove the poll interval and the empty round-trips, and park the block on a connection set aside for it so the main connection stays free.
The worker runs BZPOPMIN emq:{queue}:marker in
do_wait_for_job/3 over blocking_connection/1, a connection used
only for the block, with the main connection free for the fetch.
The pattern says block on a primitive with a timeout on a connection set aside for it; in EchoMQ that
is BZPOPMIN on the marker over blocking_connection/1, with the
main connection free for the fetch.
References
The three blocking pops behind the wait — the modern move, its older form, and the marker pop EchoMQ parks on — and the queue protocol that runs them on a dedicated connection.
Sources
- Redis — BLMOVE — the modern blocking move that parks on the work list until an element arrives.
- Redis — BRPOPLPUSH — the older blocking reliable-queue pop, the form
BLMOVEsucceeds. - Redis — BZPOPMIN — block on a sorted set until a member is added, then pop the lowest-scored; the form EchoMQ parks on.
- BullMQ — the queue protocol — the blocking worker wait, on a dedicated connection, that EchoMQ ports.
Related in this course
- R3.05 · Blocking vs polling — the module hub.
- R3.05.1 · The busy-poll cost — the polling loop the blocking pop removes.
- R3.05.3 · The marker wake-up — the producer's
ZADDthat wakes the parkedBZPOPMIN. - R3.01 · The processing list — the wait/active lists the woken worker fetches from.
- R3 · Reliable Queues — the chapter.