Databases 12 min read

Designing Redis Leaderboards: From a Single ZSet to Billion‑Scale Rankings

The article walks through the evolution of Redis leaderboard architectures, covering why ZSets are used, handling same‑score ordering, seasonal resets, access patterns, synchronous vs asynchronous updates, big‑key and hot‑key issues, sharding with buckets, and data durability strategies.

samdeepthink
samdeepthink
samdeepthink
Designing Redis Leaderboards: From a Single ZSet to Billion‑Scale Rankings

Why Leaderboards Use ZSet

ZSet supports score‑based sorting and fast lookups via a skip‑list and hash, making it a natural fit for leaderboards. Core commands are ZADD, ZRANGE/ZREVRANGE, and ZRANK/ZREVRANK, covering most leaderboard operations. When updates and reads are modest, a traditional database can suffice; Redis becomes advantageous as data volume and request pressure grow.

First Challenge: Same‑Score Ordering

Business rules often require that users with identical scores be ordered by the time they achieved the score, whereas Redis orders equal scores lexicographically by member name. The solution is to embed a secondary dimension (e.g., timestamp) into the score’s fractional part or pack score and time with bitwise operations. Because Redis scores are IEEE‑754 doubles with ~53 bits of integer precision, the encoding must balance score range, time granularity, and floating‑point limits. After encoding, updates must decode the old score, modify the primary component, re‑encode, and write back with ZADD; ZINCRBY cannot be used directly.

Seasonal Leaderboards

When weekly, monthly, or seasonal leaderboards are needed, the common mistake is to delete the existing ZSet with DEL, which instantly empties the visible ranking. The correct approach is to switch to a new key, e.g., leaderboard:season:2026-07 or leaderboard:weekly:2026-W29, and let the old key expire via TTL. Historical data is archived to MySQL for replay.

Three Access Patterns

Top N : Frequently requested, same data for all users; best served by a local cache refreshed every few seconds.

My Rank : Individual user query; cannot be cached but is fast (O(log N)) via direct ZREVRANK.

Nearby Rank : Combination of ZREVRANK to get the user’s position and ZREVRANGE to fetch surrounding entries.

Friend leaderboards often offload sorting to the client because the friend list size is limited.

Real‑time vs Asynchronous Updates

Synchronous updates write scores to Redis immediately after business logic, providing instant visibility but coupling write load to business peaks. Asynchronous updates send score changes to a message queue; a leaderboard service consumes the messages and writes to Redis, smoothing spikes and isolating failures. Synchronous is sufficient for low‑volume updates; switch to asynchronous when peak traffic impacts Redis.

Big‑Key and Hot‑Key Risks

A ZSet with tens of millions of members increases latency for ZRANGE/ZREVRANK and can block RDB snapshots or replication, affecting other workloads. A single hot key in a Redis Cluster concentrates load on one shard. Mitigations include read‑write splitting with replica reads for Top N and sharding the leaderboard across multiple keys.

When a Single ZSet Is Not Enough

Beyond memory capacity, the real bottleneck is write throughput on a single instance. Splitting the leaderboard into buckets—each a separate ZSet covering a score range—distributes writes across cluster slots. Updating a score then requires three steps (locate bucket, remove from old bucket, add to new bucket) wrapped in a Lua script for atomicity. Queries aggregate across buckets: Top N pulls from the highest bucket downward; a user’s global rank adds the counts of higher buckets to the intra‑bucket rank.

Bucket boundaries depend on score distribution; uniform scores allow equal‑size buckets, while pyramid‑shaped game scores need finer granularity in low‑score ranges. The added complexity is justified only when a single ZSet becomes a performance choke point.

Data Loss Prevention

Redis persistence (RDB snapshots and AOF) protects against data loss within Redis, but the ultimate source of truth is MySQL. Score changes are also written asynchronously to MySQL via the same MQ used for leaderboard updates. If Redis fails, the leaderboard can be rebuilt from MySQL either by re‑adding final scores with ZADD or replaying incremental changes with ZINCRBY.

Summary

Different data scales call for different solutions:

Below 10 K: Database with index, no Redis.

10 K–1 M: Single ZSet with same‑score handling and seasonal keys.

1 M–10 M: Add read‑write separation and caching.

Above 10 M (up to billions): Bucketed ZSets, cluster sharding, and asynchronous writes.

Precision requirements also vary; Top 100 must be real‑time, while ranks beyond a million can tolerate eventual consistency. Good architecture evolves with business needs rather than being overly complex from the start.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CacheshardingRedisAsynchronousZSetScalingLeaderboard
samdeepthink
Written by

samdeepthink

Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.