Case study: Real-time leaderboard
Design a real-time gaming leaderboard for 25M players using augmented skip lists (Redis sorted sets), a 1-second top-K cache, and a durable score ledger.
35 min9 steps
By the end you can
- Estimate memory footprint and update/read QPS to prove when a global leaderboard fits on a single in-memory node
- Explain how an augmented skip list computes exact player rank in O(log N) time whereas SQL COUNT(*) degrades to O(N)
- Compare user-hash partitioning and score-range bucketing when a leaderboard outgrows one machine
- Predict system behaviour under tournament-finale spikes, cache flushes, and durable ledger slowdowns
Pin down the leaderboard queries
Queries
- Update score; top 100; exact rank + five neighbours
Targets
- Updates visible <100 ms; reads p99 <50 ms
Safety
- Earned points survive memory-node loss
Do the math before sharding
Writes
2.9k/s
25M * 10 / 86,400; peak 9k/s
Reads
5.8k/s
25M * 20 / 86,400; peak 18k/s
RAM estimate
1.6 GB
25M * illustrative 64 B; measure allocator overhead
Tree depth
25
log2(25M); O(log N) rank/update
Design the API and anti-cheat boundary
Authenticate
Trusted game servers only; mTLS/HMAC; no client score writes
Update
POST /v1/leaderboards/{id}/scores; userId, delta, matchId retry key
Top
GET /v1/leaderboards/{id}/top?limit=100
Rank
GET /v1/leaderboards/{id}/users/{userId}?neighbours=5
Model the sorted set, durable ledger, and tie-breaking
rankSet
Redis ZSET; ZINCRBY/ZADD, ZREVRANK, ZREVRANGE
Durable scores
boardId + userId; append matchId log; rebuild Redis
Ties
points * 10^10 + (MAX_TIMESTAMP - achievedAt): earlier wins; require integer result <= 2^53
Assemble the architecture
Order the steps to render a player's 'My Rank & Neighbours' screen.
- 1Return the player's rank, score, and hydrated neighbour window to the client
- 2Call ZREVRANK on the sorted set to find the player's 0-based rank r in O(log N) time
- 3Call ZREVRANGE(r - 5, r + 5, WITHSCORES) to fetch the surrounding userIds and scores
- 4Batch-fetch display names and avatars for those 11 userIds from the profile cache
Deep dive: augmented skip lists and sharding past one node
Skip-list rank
- Hash finds score; traverse levels and sum spans
Hash sharding
- Merge M top lists; exact rank sums counts across all shards
Score ranges
- Counts above + local rank; crossing score moves shards
Compare the alternatives
leaderboard-sql-scan
- Rank scans saturate DB; goodput <70%
Score ranges
- Single-shard rank; rebalance buckets and move players
Top 10k + histogram
- Exact leaders; approximate percentiles elsewhere
Test the failure scenarios
Inject
Traffic doubles to 6k rps
Observe
1 s top cache and O(log N) rank
Recover
>99% goodput
Inject
Cache restarts at 1.5 s
Observe
Redis range read about 0.4 ms
Recover
Miss spike served directly
Inject
Ledger latency x5
Observe
Top/rank bypass ledger
Recover
Viewer latency unaffected
Wrap up and self-assess
Seasons
Period boards; freeze payouts; expire after 30 days
Friends
Fetch IDs, ZMSCORE, sort hundreds of scores
Recover
Rebuild sorted set from durable current-period ledger
Answer this step to continue.