jonnify·redis /redis-patterns/time-delay-priority/score-as-meaning

R4 · Time, Delay & Priority — orientation dive

Score as meaning.

The sorted set is one mechanism; the score is the whole semantic axis. Score a job by a future millisecond and the set is a delayed queue; flatten the score and the mint-ordered id serves as the queue; score it by a next-run time and the set is a scheduler registry.

Grounding: echo/apps/echo_mq · EchoMQ.Jobs.enqueue_at/6 · EchoMQ.Repeat.register/6 — three real sets, emq:{q}:schedule, emq:{q}:pending, emq:{q}:repeat, each carrying one number per member.

The score decoder — read one job three ways. Select a reading; the same fixed job stores a different score, with a different write and read command.
readout · Select a reading to see the score the same job stores, and the write and read commands the reading uses.

Pure verdict — one example job JOB0Nb1VTbfnu4, due in 90 s on a queue minted at t₀. Each reading computes the literal score: run_at_ms, 0, or next_run_ms. No fact is packed into another; each set carries one number.

§1

The score is a number that sorts

delayed-queue · sorted-sets

A sorted set stores members ordered by a numeric score. The textbook delayed queue uses the simplest possible score: a millisecond timestamp. The member is the job id; the score is when it should run; ZRANGEBYSCORE reads back everything due.

ZADD schedule 1706649000000 "JOB0Nb1VTbfnu4"
ZRANGEBYSCORE schedule -inf 1706648500000 LIMIT 0 10
ZREM schedule "JOB0Nb1VTbfnu4"

The member is the job id, the score is the fire-time, and the set stays ordered by fire-time with no extra bookkeeping. Nothing in Valkey is told what the score "means" — it is a number, and the set sorts by it. The meaning lives in how the writer chooses the number and how the reader interprets the order. That freedom is the lever: because the score is an arbitrary number, the member can carry meaning too. EchoMQ uses this on its pending set — it flattens every score to 0 and lets the branded job id, which sorts as its mint instant, carry the order.

The score is a number, and the set sorts by it; the meaning is the writer's choice, not the engine's.

§2

One number, three real sets

jobs.ex · repeat.ex

EchoMQ never packs two facts into one score. It keeps each reading on its own set and lets each set carry exactly one number per member.

  • Fire-time — emq:{q}:schedule. EchoMQ.Jobs.enqueue_at/6 writes the caller's absolute run-at millisecond; enqueue_in/6 computes now + delay_ms from the server clock inside the @schedule Lua. The score is the millisecond.
  • Order — emq:{q}:pending. EchoMQ.Jobs.promote/3 ZADDs a due job with score 0. Every member shares score 0, so the set sorts by the branded JOB id; the claim script's ZPOPMIN serves the oldest.
  • Next-run — emq:{q}:repeat. EchoMQ.Repeat.register/6 ZADDs a name scored by its next-run millisecond, and the @register Lua upserts — it guards on EXISTS the record hash and returns 0 without re-adding when the name is already live, so a reboot adds no duplicate.
The three-readings panel. Hover or select a reading to light its set and read the write and read commands. The same job lands in any of the three.
emq:{q}:schedulescore = run_at_msZADD · ZRANGEBYSCORE emq:{q}:pendingscore = 0 (id orders)ZADD · ZPOPMIN emq:{q}:repeatscore = next_run_msZADD upsert · ZRANGEBYSCORE
readout · Hover or select a set to read its score and its write/read commands. One job can land in any of the three; the set name fixes what its number means.

Three sorted sets, one mechanism. The commands stay constant — ZADD to write, ZRANGEBYSCORE or ZPOPMIN to read — and the score is the whole semantic axis.

§3

The writer encodes the meaning

echo_mq/jobs.ex · @schedule

The textbook reaches the same "next job to serve" order by packing two fields into one score — a priority tier in the high 32 bits, an arrival counter in the low 32 bits (score = priority × 2³² + arrival), read by ZPOPMIN. That is a valid encoding, and it is the contrast worth knowing: it puts the order in the score. EchoMQ chose the opposite — it puts the order in the member. A score-0 pending set ordered by a mint-ordered branded id needs no arrival counter to break ties, because the id already is one.

echo/apps/echo_mq · the schedule write

The run-at score, as the inline Lua computes it

The @schedule script writes the run-at millisecond — the caller's absolute ms for at, the server clock plus the delay for in. The body is verbatim from jobs.ex:

echo_mq/jobs.ex · @schedule (the run-at score)frozen
local score
if ARGV[3] == 'in' then
  local t = redis.call('TIME')
  local now = t[1] * 1000 + math.floor(t[2] / 1000)
  score = now + tonumber(ARGV[4])        ← server clock + delay
else
  score = tonumber(ARGV[4])              ← the caller's absolute run-at ms
end
redis.call('ZADD', KEYS[2], score, ARGV[1])
the pattern

The sorted set does not store time, priority, or schedule — it stores one number per member and keeps the set ordered by it. The number is the meaning, and the writer encodes it.

its EchoMQ application

Three real sets — emq:{q}:schedule scored by run-at ms, emq:{q}:pending scored 0 and ordered by the mint-ordered JOB id, emq:{q}:repeat scored by next-run ms and upserted — each carrying exactly one number per member.

Notes on ValkeyThe @register upsert reads EXISTS on the per-name record hash before ZADD, so a re-register is idempotent; ZADD would otherwise overwrite the score and could double-schedule a name — valkey.io/commands/zadd.

The number is the meaning, and the writer encodes it. EchoMQ's choice is to keep each number simple and let the branded id do the ordering work a packed counter would otherwise carry.

§4

References

sources · related

Sources

  • Valkey — Topics — the sorted-set type: members ordered by a numeric score, the substrate every reading shares.
  • Redis — ZADD — add or update a member's score; the one write behind all three readings, and the repeat upsert.
  • Redis — ZRANGEBYSCORE — read members by score range; the due-head read the promote and repeat sweeps both use.
  • Redis — ZPOPMIN — pop the lowest-scored member; the read that serves the oldest job from the score-0 pending set.

Related in this course