R4.03.1 · dive 1
Packing two keys in one score
A priority queue needs two orderings at once. EchoMQ packs both into one number —
priority × 0x100000000 + counter — the tier in the high bits, the arrival
in the low.
The whole pattern rests on one decision: what number goes in the score. A delayed queue puts the fire-time there; a priority queue puts a composite there — a number whose high thirty-two bits carry the priority tier and whose low thirty-two carry the arrival order. Move the sliders on the right to set a priority and a counter and watch the one score they pack, split into its two fields.
The composite score · tier × 0x100000000 + counter
The problem: two orderings, one score
A sorted set offers one ordering axis — the score. A priority queue needs two. Jobs in a higher-priority tier must come out before jobs in a lower tier, and within one tier the earliest-arriving job must come out first. A single number has to express both.
The move is positional. Reserve the high bits of the score for the tier and the low bits for the arrival order. Then the score compares tier-first automatically: any difference in the high bits dominates any difference in the low bits, because the high bits are worth more. Comparing two composite scores compares their tiers first and, only when the tiers are equal, their arrival counters.
A sorted set offers one ordering axis; a priority queue needs two, so the score reserves its high bits for the tier and its low bits for the arrival order, and the high bits dominate.
The formula
EchoMQ computes the score with getPriorityScore, an included Lua function whose
canonical home is addPrioritizedJob-9.lua.
-- getPriorityScore.lua (included by addPrioritizedJob-9) — real local function getPriorityScore(priority, priorityCounterKey) local prioCounter = rcall("INCR", priorityCounterKey) return priority * 0x100000000 + prioCounter % 0x100000000 end
0x100000000 is 232 —
4294967296. Multiplying the priority by it shifts the tier left thirty-two binary
places, leaving the low thirty-two bits at zero. The arrival counter, taken
% 232, then fills those low bits. The result is one number whose top half
is the tier and whose bottom half is the position within the tier.
One IEEE double carries the whole thing exactly. A double holds fifty-three bits of integer precision; a small priority shifted into the high thirty-two bits plus a counter in the low thirty-two stays well under that ceiling, so the score is exact and the ordering never rounds.
The score is priority × 0x100000000 + (INCR pc): the tier shifted into
the high thirty-two bits, the arrival counter in the low — one double carries both exactly.
The tier dominates the counter
Because the tier sits in the high bits, any change in the tier outweighs every value the counter can take. Two
jobs in tier 1, however far apart their counters, both score below any job in tier 2 — the smallest tier-2
score is 2 × 232, larger than the largest tier-1 score, which is
1 × 232 + (232 − 1). The high bits set the band; the
low bits position within it.
The hero figure above shows it directly: raise the priority slider by one and the score jumps by a full
0x100000000, dwarfing any counter move. The interactive below makes the comparison
explicit, pitting two jobs against each other — one a step higher in tier, the other far ahead on the
counter — and reporting which composite score is smaller and therefore pops first.
Tier beats counter · which job pops first
The tier sits in the high bits, so a change of tier outweighs any counter value; a tier-1 job at the top of its band still scores below the smallest tier-2 job.
A lower number wins
The formula makes a higher-precedence job a smaller number: priority 1 produces a smaller high-bit field than
priority 2, so its composite score is smaller. The consume command is ZPOPMIN, which
returns the smallest score first. The two compose into the convention a queue user sees: a
lower priority number means higher precedence. Priority 1 beats priority 2 because 1 makes the
smaller score and ZPOPMIN pops the smallest. The third dive walks that consume in real
code.
A lower priority number makes a smaller score, and ZPOPMIN pops the smallest
first, so a lower priority number is served sooner — the precedence is encoded as "smaller is sooner".
In EchoMQ — the composite score, in real code
The whole score is real code in echo/apps/echomq. The prioritized set is
EchoMQ.Keys.prioritized/1 → emq:{queue}:prioritized. The
arrival-counter key is EchoMQ.Keys.pc/1 → emq:{queue}:pc.
The public entry EchoMQ.Scripts.add_prioritized_job/4 runs
addPrioritizedJob-9.lua, passing Keys.pc(ctx) as
KEYS[9]; the script calls getPriorityScore and then
rcall("ZADD", prioritizedKey, score, jobId).
-- addPrioritizedJob-9.lua — score the job, then add it (real) local score = getPriorityScore(priority, priorityCounterKey) -- priority × 0x100000000 + INCR pc rcall("ZADD", prioritizedKey, score, jobId) -- place the job at its composite score
The same getPriorityScore is reused verbatim across the priority-aware scripts, so
every path that scores a prioritized job packs the tier and counter the same way. The number chosen to carry the
meaning is the whole pattern: the bare priority says only "this tier"; the composite says "this tier, and this
position within it".
A composite key packs two fields into one ordering key — the source packs them into the member and
reads with ZRANGEBYLEX.
getPriorityScore packs them into the score:
priority × 0x100000000 + (INCR pc) — the tier in the high thirty-two bits,
the arrival counter in the low — so one numeric score orders by tier first and arrival second, read with
one ZPOPMIN.
A composite key can pack into the member or the score; EchoMQ packs the tier and the arrival counter into one score, so the high bits set the tier and the low bits keep arrival order, in a single number.
References
The write that scores a prioritized job by its composite number, the sorted set behind it, the atomic counter folded into the low bits, and the queue protocol EchoMQ ports.
Sources
- Redis — ZADD — add a member with its score; the write that scores a prioritized job by its composite number.
- Redis — Sorted sets — score-then-member ordering, the axis the composite score occupies.
- Redis — INCR — the atomic increment that produces the arrival counter folded into the low bits.
- BullMQ — the queue protocol — the prioritized-jobs protocol EchoMQ ports, where the Lua scripts are the protocol.
Related in this course
- R4.03 · Priority with composite scores — the module hub.
- R4.03.2 · FIFO within a tier — the next dive: the counter that breaks the within-tier tie.
- R4.03.3 · ZPOPMIN — the smallest-score pop that reads this score.
- R4.01.1 · The score is the fire-time — the sibling score, shifted twelve bits for time.
- R4 · Time, Delay & Priority — the chapter.
- E4 · Groups — the dedicated EchoMQ course: intra-group priority in depth.