Skip to content

Ask the HotKV docs

Answers come only from these docs and can be wrong; check the linked pages before you rely on them.

Sorted sets

Last updated

A sorted set is a set of unique strings where every member carries a floating-point score, and members are kept ordered by that score (members with equal scores are ordered by their bytes). You can fetch members by rank, by score range, or by lexicographic range, and find any member’s rank quickly. Sorted sets power leaderboards, priority queues, scheduling (“everything due before now”), sliding-window rate limits, and secondary indexes by number or date.

ZADD adds members with scores, or updates the score of members that are already there. It replies with the number of new members.

127.0.0.1:6379> ZADD leaderboard 1200 "ann" 950 "bob" 1430 "cid"
(integer) 3
127.0.0.1:6379> ZSCORE leaderboard "bob"
"950"
127.0.0.1:6379> ZINCRBY leaderboard 300 "bob"
"1250"
127.0.0.1:6379> ZRANGE leaderboard 0 -1 WITHSCORES
1) "ann"
2) "1200"
3) "bob"
4) "1250"
5) "cid"
6) "1430"
127.0.0.1:6379> ZRANGE leaderboard 0 1 REV
1) "cid"
2) "bob"
127.0.0.1:6379> ZREVRANK leaderboard "ann"
(integer) 2
127.0.0.1:6379> ZCARD leaderboard
(integer) 3
127.0.0.1:6379> ZREM leaderboard "ann"
(integer) 1

Ranks start at 0. ZRANK counts from the lowest score and ZREVRANK from the highest; both accept WITHSCORE to return the score too.

Option Effect
NX Only add new members, never update existing ones
XX Only update existing members, never add
GT / LT Only update when the new score is greater / less than the current one
CH Reply with the number of members added or changed, not only added
INCR Add the score to the current one, like ZINCRBY, and reply with the new score

GT is the natural choice for “keep each player’s best score”.

ZRANGE is the one command for all three kinds of range. By rank (the default), by score with BYSCORE, or by member name with BYLEX; REV reverses the order and LIMIT offset count pages through the result.

127.0.0.1:6379> ZADD jobs 1767225600 "job:a" 1767229200 "job:b" 1767232800 "job:c"
(integer) 3
127.0.0.1:6379> ZRANGE jobs -inf 1767229200 BYSCORE
1) "job:a"
2) "job:b"
127.0.0.1:6379> ZRANGE jobs (1767225600 +inf BYSCORE LIMIT 0 1
1) "job:b"
127.0.0.1:6379> ZCOUNT jobs 1767229200 +inf
(integer) 2

A ( before a score makes that end exclusive; -inf and +inf are open ends. For BYLEX, ranges are written [a (inclusive), (a (exclusive), - and +, and are meant for sets where every member has the same score. The older commands (ZRANGEBYSCORE, ZREVRANGE, ZRANGEBYLEX, …) still work.

Leaderboard. ZADD board GT <score> <player> records a score only when it beats the player’s best, ZRANGE board 0 9 REV WITHSCORES shows the top ten, and ZREVRANK board <player> gives a player’s position.

Priority queue. Score by priority and pop the most urgent item with ZPOPMIN (or ZPOPMAX). BZPOPMIN and BZMPOP block until an item arrives.

Delayed jobs. Score each job by the Unix time it is due. A worker runs ZRANGE jobs -inf <now> BYSCORE LIMIT 0 10 and removes what it takes with ZREM, inside a transaction or Lua script so two workers never take the same job.

Sliding-window rate limit. For each request, ZADD a unique member scored by the current time in milliseconds, ZREMRANGEBYSCORE everything older than the window, and ZCARD to count what is left. See Rate limiting for the full recipe and HotKV’s built-in alternatives.

Index by number or date. Keep ZADD users:by-signup <timestamp> <user-id> alongside your hashes to answer “users who signed up last week”.

ZUNION, ZINTER and ZDIFF combine sorted sets, adding up scores by default. WEIGHTS multiplies each input’s scores and AGGREGATE MIN | MAX | SUM picks how scores are merged. The ...STORE forms write the result to a key, and ZINTERCARD counts the intersection only. In a cluster, all the keys must share a hash slot.

A time to live applies to the whole sorted set; members cannot expire one by one. The usual way to age out members is to score them by time and remove old ones with ZREMRANGEBYSCORE.

Scores are 64-bit floating-point numbers, so integers are exact up to 2^53. Members are strings up to proto-max-bulk-len (512 MB by default). Adding, removing, scoring and ranking a member take logarithmic time; range queries cost that plus the number of members returned.

Command What it does
ZADD Add members or update scores
ZINCRBY Add to a member’s score
ZREM Remove members
ZSCORE / ZMSCORE Scores of members
ZRANK / ZREVRANK Rank of a member
ZCARD Number of members
ZCOUNT / ZLEXCOUNT Members in a score or lex range
ZRANGE Members by rank, score, or lex range
ZRANGESTORE Store a range in another key
ZRANGEBYSCORE / ZREVRANGEBYSCORE Older score-range forms
ZRANGEBYLEX / ZREVRANGEBYLEX Older lex-range forms
ZREVRANGE Older reverse rank-range form
ZPOPMIN / ZPOPMAX Remove and return the lowest or highest
ZMPOP Pop from the first non-empty sorted set
BZPOPMIN / BZPOPMAX / BZMPOP Blocking pops
ZRANDMEMBER Random members
ZSCAN Iterate members in pages
ZREMRANGEBYSCORE / ZREMRANGEBYRANK / ZREMRANGEBYLEX Remove a range
ZUNION / ZUNIONSTORE Union
ZINTER / ZINTERSTORE Intersection
ZINTERCARD Size of the intersection
ZDIFF / ZDIFFSTORE Difference
import hotkv
client = hotkv.HotKV(host="localhost", port=6379, decode_responses=True)
client.zadd("leaderboard", {"ann": 1200, "bob": 950, "cid": 1430})
client.zincrby("leaderboard", 300, "bob")
top = client.zrange("leaderboard", 0, 2, rev=True, withscores=True)
print(top)
print(client.zrevrank("leaderboard", "ann")) # 2
client.close()