jonnifyredis

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

12s
30s
blocking connection · BZPOPMIN marker, timeout BLOCKED — parked on the server woke — round-trips used: 1

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 BLMOVE succeeds; 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?

blocking BZPOPMIN marker parked SET lock (a main-line command) — — —

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
the pattern

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.

in EchoMQ

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.

Door · not depth The blocking connection's full lifecycle — how it is created, registered, closed, and reconnected, and how each runtime manages its connection pool — is the dedicated EchoMQ course. From here, R3.05.2 opens onto E2 · the engine, the as-built worker fetch loop and its connections. This dive teaches the blocking pop; that course teaches the engine that parks on it.

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 BLMOVE succeeds.
  • 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