Mastering Redis ZSet: Real‑Time Ranking, Set vs List, and Underlying Implementations
This article walks through a real‑world sales‑ranking scenario, explains why a simple SQL solution falls short at scale, and demonstrates how Redis Set and ZSet data structures provide high‑performance, real‑time ranking, including detailed command examples, performance metrics, and an in‑depth look at ZSet's internal listpack and skiplist‑dict implementations.
In a typical e‑commerce use case, JD.com needs to build a daily sales‑ranking for each brand in real time. A straightforward SQL query that groups by brand and orders by total sales works for small volumes, but when daily orders reach millions, the query becomes slow and resource‑intensive, and extending it to monthly rankings is impractical.
What is Set?
A Redis Set is an unordered collection of unique elements, ideal for operations such as intersection, union, and difference. It is useful for scenarios like finding common followers or shared interests.
Example commands:
redis> SADD Tony Mary // Mary follows Tony
(redis) (integer) 1
redis> SADD Tony Lynn // Lynn follows Tony
(redis) (integer) 1
redis> SMEMBERS Tony // List Tony's followers
1) "Mary"
2) "Lynn"
redis> SADD Tom Mary // Mary follows Tom
(redis) (integer) 1
redis> SADD Tom Eric // Eric follows Tom
(redis) (integer) 1
redis> SMEMBERS Tom // List Tom's followers
1) "Mary"
2) "Eric"
redis> SINTER Tony Tom // Common followers of Tony and Tom
1) "Mary"
redis> SUNION Tony Tom // All followers of Tony and Tom
1) "Mary"
2) "Lynn"
3) "Eric"
redis> SDIFF Tony Tom // Followers of Tony not following Tom
1) "Lynn"
redis> SDIFF Tom Tony // Followers of Tom not following Tony
1) "Eric"Key differences between Set and List :
List allows duplicate elements; Set does not.
List preserves insertion order; Set is unordered.
What is ZSet?
A Redis ZSet (Sorted Set) extends Set by associating each member with a numeric score, enabling ranking and range queries. In the sales‑ranking example, the score represents the brand's total sales.
Example ZSet commands for the ranking scenario:
redis> ZADD 家电全品类 5.5 海尔 // Add brand "海尔" with 5.5 billion sales
(redis) (integer) 1
redis> ZADD 家电全品类 4.5 美的 // Add brand "美的" with 4.5 billion sales
(redis) (integer) 1
redis> ZADD 家电全品类 3.2 小米 // Add brand "小米" with 3.2 billion sales
(redis) (integer) 1
redis> ZADD 家电全品类 2.7 格力 // Add brand "格力" with 2.7 billion sales
(redis) (integer) 1
redis> ZCARD 家电全品类 // Number of elements
(integer) 4
redis> ZSCORE 家电全品类 格力 // Get sales for "格力"
"2.7"
redis> ZREVRANGE 家电全品类 0 -1 WITHSCORES // Descending order
1) "海尔"
2) "5.5"
3) "美的"
4) "4.5"
5) "小米"
6) "3.2"
7) "格力"
8) "2.7"
redis> ZRANGE 家电全品类 0 -1 WITHSCORES // Ascending order
1) "格力"
2) "2.7"
3) "小米"
4) "3.2"
5) "美的"
6) "4.5"
7) "海尔"
8) "5.5"
redis> ZINCRBY 家电全品类 2.2 格力 // Increase "格力" sales by 2.2
"4.9"
redis> ZREVRANGE 家电全品类 0 -1 WITHSCORES // Updated ranking
1) "海尔"
2) "5.5"
3) "格力"
4) "4.9"
5) "美的"
6) "4.5"
7) "小米"
8) "3.2"Benchmark data from the Redis performance whitepaper shows ZAdd handling millions of entries in sub‑millisecond latency, and similar performance for ZIncrBy and range queries when the read range is limited. In a Redis Cluster, limit the range of ZREVRANGE to avoid hot keys.
Why ZSet Performs Well in Ranking Scenarios
Space‑for‑time trade‑off: pre‑allocated structures enable fast lookups.
Highly optimized low‑level implementation.
ZSet Internal Implementation
Redis 7 implements ZSet using three structures: a skiplist , a hash table (dict) , and a listpack for compact storage when the element count is small.
Selection rules: if the number of elements is < 128 and each element’s encoded size is < 64 bytes, Redis stores the ZSet as a listpack; otherwise it uses the skiplist + dict combination.
listpack
listpack is an optimized version of the older ziplist. Its layout includes total_bytes, size, and a terminating byte, followed by a series of entries. Each entry contains: prevlen: length of the previous entry (1 byte if < 254, otherwise 5 bytes). encoding: type and length of the current entry (1, 2, or 5 bytes). data: the actual payload.
Advantages: compact memory layout, contiguous storage, and better CPU‑cache utilization.
Disadvantages: performance degrades when the number of elements grows large; suitable for small, space‑constrained sets.
skiplist + dict
For larger ZSets, Redis stores elements in a skiplist for ordered traversal and in a hash table for O(1) member‑to‑score lookups. Both structures share the same object pointer, avoiding duplicate memory.
Using only a skiplist would make ZSCORE O(log N) instead of O(1); using only a dict would require O(N log N) time and O(N) extra memory for range queries. The combined approach gives fast score lookups and efficient range queries.
Conclusion
Redis Set and ZSet provide powerful, high‑performance solutions for real‑time ranking and set operations, overcoming the scalability limits of traditional SQL approaches. Understanding their command syntax, performance characteristics, and internal data structures—listpack for compact small sets and skiplist + dict for large, ordered sets—enables developers to choose the right implementation for their specific workload.
Senior Tony
Former senior tech manager at Meituan, ex‑tech director at New Oriental, with experience at JD.com and Qunar; specializes in Java interview coaching and regularly shares hardcore technical content. Runs a video channel of the same name.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
