ZSETs offer the ability to store a mapping of members to scores (similar to the keys and values of HASHes). These mappings allow us to manipulate the numeric scores,2 and fetch and scan over both members and scores based on the sorted order of the scores. In chapter 1, we showed a brief example that used ZSETs as a way of sorting submitted articles based on time and how many up-votes they had received, and in chapter 2, we had an example that used ZSETs as a way of handling the expiration of old cookies.
In this section, we’ll talk about commands that operate on ZSETs. You’ll learn how to add and update items in ZSETs, as well as how to use the ZSET intersection and union commands. When finished with this section, you’ll have a much clearer understanding about how ZSETs work, which will help you to better understand what we did with them in chapter 1, and how we’ll use them in chapters 5, 6, and 7.
Let’s look at some commonly used ZSET commands in table 3.9.
Command | Example use and description |
---|---|
ZADD | ZADD key-name score member [score member …] — Adds members with the given scores to the ZSET |
ZREM | ZREM key-name member [member …] — Removes the members from the ZSET, returning the number of members that were removed |
ZCARD | ZCARD key-name — Returns the number of members in the ZSET |
ZINCRBY | ZINCRBY key-name increment member — Increments the member in the ZSET |
ZCOUNT | ZCOUNT key-name min max — Returns the number of members with scores between the provided minimum and maximum |
ZRANK | ZRANK key-name member — Returns the position of the given member in the ZSET |
ZSCORE | ZSCORE key-name member — Returns the score of the member in the ZSET |
ZRANGE | ZRANGE key-name start stop [WITHSCORES] — Returns the members and optionally the scores for the members with ranks between start and stop |
We’ve used some of these commands in chapters 1 and 2, so they should already be familiar to you. Let’s quickly revisit the use of some of our commands.
>>> conn.zadd('zset-key', 'a', 3, 'b', 2, 'c', 1) 3
Adding members to ZSETs in Python has the arguments reversed compared to standard Redis, which makes the order the same as HASHes.
>>> conn.zcard('zset-key') 3
Knowing how large a ZSET is can tell us in some cases if it’s necessary to trim our ZSET.
>>> conn.zincrby('zset-key', 'c', 3) 4.0
We can also increment members like we can with STRING and HASH values.
>>> conn.zscore('zset-key', 'b') 2.0
Fetching scores of individual members can be useful if we’ve been keeping counters or toplists.
>>> conn.zrank('zset-key', 'c') 2
By fetching the 0-indexed position of a member, we can then later use ZRANGE to fetch a range of the values easily.
>>> conn.zcount('zset-key', 0, 3) 2L
Counting the number of items with a given range of scores can be quite useful for some tasks.
>>> conn.zrem('zset-key', 'b') True
Removing members is as easy as adding them.
>>> conn.zrange('zset-key', 0, -1, withscores=True) [('a', 3.0), ('c', 4.0)]
For debugging, we usually fetch the entire ZSET with this ZRANGE call, but real use cases will usually fetch items a relatively small group at a time.
You’ll likely remember our use of ZADD, ZREM, ZINCRBY, ZSCORE, and ZRANGE from chapters 1 and 2, so their semantics should come as no surprise. The ZCOUNT command is a little different than the others, primarily meant to let you discover the number of values whose scores are between the provided minimum and maximum scores.
Table 3.10 shows several more ZSET commands in Redis that you’ll find useful.
Command | Example use and description |
---|---|
ZREVRANK | ZREVRANK key-name member — Returns the position of the member in the ZSET, with members ordered in reverse |
ZREVRANGE | ZREVRANGE key-name start stop [WITHSCORES] — Fetches the given members from the ZSET by rank, with members in reverse order |
ZRANGEBYSCORE | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] — Fetches the members between min and max |
ZREVRANGEBYSCORE | ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] — Fetches the members in reverse order between min and max |
ZREMRANGEBYRANK | ZREMRANGEBYRANK key-name start stop — Removes the items from the ZSET with ranks between start and stop |
ZREMRANGEBYSCORE | ZREMRANGEBYSCORE key-name min max — Removes the items from the ZSET with scores between min and max |
ZINTERSTORE | ZINTERSTORE dest-key key-count key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX] — Performs a SET-like intersection of the provided ZSETs |
ZUNIONSTORE | ZUNIONSTORE dest-key key-count key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX] — Performs a SET-like union of the provided ZSETs |
This is the first time that you’ve seen a few of these commands. If some of the ZREV* commands are confusing, remember that they work the same as their nonreversed counterparts, except that the ZSET behaves as if it were in reverse order (sorted by score from high to low). You can see a few examples of their use in the next listing.
>>> conn.zadd('zset-1', 'a', 1, 'b', 2, 'c', 3) 3 >>> conn.zadd('zset-2', 'b', 4, 'c', 1, 'd', 0) 3
We’ll start out by creating a couple of ZSETs.
>>> conn.zinterstore('zset-i', ['zset-1', 'zset-2']) 2L >>> conn.zrange('zset-i', 0, -1, withscores=True) [('c', 4.0), ('b', 6.0)]
When performing ZINTERSTORE or ZUNIONSTORE, our default aggregate is sum, so scores of items that are in multiple ZSETs are added.
>>> conn.zunionstore('zset-u', ['zset-1', 'zset-2'], aggregate='min') 4L >>> conn.zrange('zset-u', 0, -1, withscores=True) [('d', 0.0), ('a', 1.0), ('c', 1.0), ('b', 2.0)]
It’s easy to provide different aggregates, though we’re limited to sum, min, and max.
>>> conn.sadd('set-1', 'a', 'd') 2 >>> conn.zunionstore('zset-u2', ['zset-1', 'zset-2', 'set-1']) 4L >>> conn.zrange('zset-u2', 0, -1, withscores=True) [('d', 1.0), ('a', 2.0), ('c', 4.0), ('b', 6.0)]
We can also pass SETs as inputs to ZINTERSTORE and ZUNIONSTORE; they behave as though they were ZSETs with all scores equal to 1.
ZSET union and intersection can be difficult to understand at first glance, so let’s look at some figures that show what happens during the processes of both intersection and union. Figure 3.1 shows the intersection of the two ZSETs and the final ZSET result. In this case, our aggregate is the default of sum, so scores are added.
Unlike intersection, when we perform a union operation, items that exist in at least one of the input ZSETs are included in the output. Figure 3.2 shows the result of performing a union operation with a different aggregate function, min, which takes the minimum score if a member is in multiple input ZSETs.
In chapter 1, we used the fact that we can include SETs as part of ZSET union and intersection operations. This feature allowed us to easily add and remove articles from groups without needing to propagate scoring and insertion times into additional ZSETs. Figure 3.3 shows a ZUNIONSTORE call that combines two ZSETs with one SET to produce a final ZSET.
In chapter 7, we’ll use ZINTERSTORE and ZUNIONSTORE as parts of a few different types of search. We’ll also talk about a few different ways to combine ZSET scores with the optional WEIGHTS parameter to further extend the types of problems that can be solved with SETs and ZSETs.
As you’re developing applications, you may have come upon a pattern known as publish/subscribe, also referred to as pub/sub. Redis includes this functionality, which we’ll cover next.
2 Scores are actually stored inside Redis as IEEE 754 floating-point doubles.