Queues & jobs
Hand work to a worker,
and get it back if the worker dies
A queue table in a relational database is a locking convention with extra steps. kevy has two real queues: a list when losing a job is survivable, and a stream with consumer groups when it is not.
Which of the two
Use a list when the job is cheap to redo and the worker is unlikely to die mid-task — sending an email, warming a cache, invalidating a CDN path. BRPOP blocks until there is work, so the worker does not poll.
Use a stream when the job must not be lost. A consumer group hands each message to exactly one worker and remembers that it did. If the worker dies before acknowledging, the message stays in the pending list and another worker can claim it — that is the whole reason streams exist, and it is the difference between a queue and a hope.
How — a list, for work you can redo
# producer
LPUSH jobs:email '{"to":"ada@example.com","tpl":"welcome"}'
# worker — blocks until there is something, up to 30 seconds
BRPOP jobs:email 30
-> 1) "jobs:email"
2) "{\"to\":\"ada@example.com\",\"tpl\":\"welcome\"}"
# a delayed job: the score is when it is due.
# ZPOPMIN.BELOW is kevy's own — it takes only what is actually due,
# and stops at the first job that is not.
ZADD jobs:due 1783875499 '{"id":"j-91"}'
ZPOPMIN.BELOW jobs:due 1783875500
-> 1) the job payload
2) 1783875499
How — a stream, for work you cannot lose
# once, at setup
XGROUP CREATE jobs:pay g1 $ MKSTREAM
# producer
XADD jobs:pay * order 4410 amount 8400
# worker: read, then work, then acknowledge
XREADGROUP GROUP g1 worker-3 COUNT 1 BLOCK 5000 STREAMS jobs:pay >
XACK jobs:pay g1 1783875499458-0
# the worker died before XACK. another one takes over:
XAUTOCLAIM jobs:pay g1 worker-7 60000 0-0
# claims anything idle for more than 60 s
# what is still outstanding, and who has it
XPENDING jobs:pay g1A stream is not free. Trimming with MAXLEN recomputes the stream's weight, which is O(N) in the whole stream — so trim on a schedule, not on every XADD. XREADGROUP's COUNT bounds what you are handed, not what is scanned: the entire undelivered tail is materialised first. And on a multi-shard server, BLPOP across several keys does not honour Redis's strict left-to-right priority — keys on the connection's own shard are served first. All of this is per-command in the reference.