Sorted sets
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.
Create, read, update, delete
Section titled “Create, read, update, delete”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) 3127.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 WITHSCORES1) "ann"2) "1200"3) "bob"4) "1250"5) "cid"6) "1430"127.0.0.1:6379> ZRANGE leaderboard 0 1 REV1) "cid"2) "bob"127.0.0.1:6379> ZREVRANK leaderboard "ann"(integer) 2127.0.0.1:6379> ZCARD leaderboard(integer) 3127.0.0.1:6379> ZREM leaderboard "ann"(integer) 1Ranks start at 0. ZRANK counts from the lowest score
and ZREVRANK from the highest; both accept
WITHSCORE to return the score too.
ZADD options
Section titled “ZADD options”| 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”.
Range queries
Section titled “Range queries”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) 3127.0.0.1:6379> ZRANGE jobs -inf 1767229200 BYSCORE1) "job:a"2) "job:b"127.0.0.1:6379> ZRANGE jobs (1767225600 +inf BYSCORE LIMIT 0 11) "job:b"127.0.0.1:6379> ZCOUNT jobs 1767229200 +inf(integer) 2A ( 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.
Common patterns
Section titled “Common patterns”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”.
Combining sorted sets
Section titled “Combining sorted sets”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.
Expiry
Section titled “Expiry”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.
Limits and performance
Section titled “Limits and performance”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.
Commands
Section titled “Commands”| 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 |
From your application
Section titled “From your application”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")) # 2client.close()import { HotKV } from "@hotkv/client";
const client = new HotKV({ url: "hotkv://127.0.0.1:6379" });
await client.zAdd("leaderboard", [["ann", "1200"], ["bob", "950"], ["cid", "1430"]]);await client.zIncrBy("leaderboard", 300, "bob");const top = await client.zRange("leaderboard", "0", "2", { rev: true, withScores: true });console.log(top);await client.close();client, err := hotkv.NewClient(&hotkv.Options{Addr: "localhost:6379"})if err != nil { log.Fatal(err)}defer client.Close()ctx := context.Background()
client.ZAdd(ctx, "leaderboard", hotkv.Z{Score: 1200, Member: "ann"}, hotkv.Z{Score: 950, Member: "bob"}, hotkv.Z{Score: 1430, Member: "cid"})client.ZIncrBy(ctx, "leaderboard", 300, "bob")top, _ := client.ZRevRange(ctx, "leaderboard", 0, 2).Result()fmt.Println(top)import com.hotkv.HotKV;import java.util.Map;
try (HotKV client = HotKV.create("hotkv://localhost:6379")) { client.zadd("leaderboard", Map.of("ann", 1200.0, "bob", 950.0, "cid", 1430.0)); client.zincrBy("leaderboard", 300, "bob"); System.out.println(client.zrevRangeWithScores("leaderboard", 0, 2)); System.out.println(client.zrank("leaderboard", "ann"));}Was this page helpful?
Thanks for the feedback.