Build a Live Leaderboard for a 10M-Player Tournament
scalability databases performance
System Design Deep Dive
Live Tournament Leaderboard
How to serve real-time rankings for 10 million concurrent players in under 10ms.
Picture the final hour of a Fortnite Championship Series event. Ten million players are in active matches. Every elimination, every placement bonus, every storm survival second ticks upward on their score. The moment a match ends and scores are submitted, players sprint to the leaderboard screen to see whether they cracked the top 100 - and whether the player who was ranked 97th an hour ago is now ranked 104th and out of the prize pool. This is not a slow read-and-display problem. This is a live, high-velocity write problem where the read surface must stay instantaneous under crushing load.
The numbers are unforgiving. At peak submission windows - the last 30 minutes of a tournament round when thousands of matches resolve simultaneously - score updates arrive at 50,000 per second. Each update must find the player’s current entry, compare it to the incoming score, apply the update if it is higher, and recompute that player’s rank relative to all 10 million other players. The top-100 leaderboard is requested by players, spectators, and broadcast overlays at roughly 200,000 reads per second. Each of those reads must complete in under 10ms.
The personal rank query is a separate beast. A player finishing a match immediately types “where am I?” They want their rank among 10 million peers, not just whether they made the top 100. This query - “given my score, how many players have a strictly higher score?” - looks trivial but is surprisingly expensive at 10M entries. Doing a full table scan on every personal rank query would saturate any database that exists. The answer must come back in under 5ms.
We need to solve for high-velocity score ingest without write amplification, sub-10ms top-N serving with minimal cache invalidation churn, and sub-5ms personal rank estimation that degrades gracefully to approximation rather than failing under load simultaneously.
Requirements and Constraints
Functional Requirements
- Accept score updates for any of the 10 million active players in real time during a tournament window
- Serve the live top-100 leaderboard globally (rank, player ID, score, display name)
- Answer personal rank queries: “what rank is player X right now?”
- Enforce score monotonicity: only accept a new score if it is strictly greater than the player’s current score (scores can only increase within a tournament)
- Support tie-breaking by insertion timestamp: players with identical scores are ranked by who achieved that score first
Non-Functional Requirements
- Write throughput: 50,000 score updates per second sustained, 80,000 at peak (match-end bursts)
- Top-100 read latency: under 10ms at P99 globally
- Personal rank latency: under 5ms at P99
- Availability: 99.99% during the active tournament window (4-8 hours)
- Consistency: top-100 list reflects all score updates within 1 second of submission; personal rank may be approximate within +/- 500 positions at the 10M scale
- Storage: 10M player entries with score + metadata; approximately 400MB per sorted set
Constraints and Assumptions
- The tournament has a defined start and end time; the leaderboard is read-heavy during the event and cold outside it
- Scores are non-negative integers, updated at most once every 10 seconds per player (game engine rate limit)
- Score updates may arrive out of order due to network jitter; the system must apply only higher scores
- We accept rank approximation for personal rank queries beyond the top 10,000 positions
- The top-100 list is the globally authoritative number; it must be exact, not approximated
High-Level Architecture
The system splits into a write path that ingests score updates and a read path that serves rankings.
Score updates arrive from Game Servers via a REST or gRPC endpoint at the Score Ingestion Service. Rather than writing directly to the leaderboard store, the ingestion service enqueues each update to Kafka (partitioned by player_id to preserve per-player ordering). This decouples the game server from the leaderboard’s write speed and provides a durable buffer during burst windows.
The Leaderboard Compute Service consumes from Kafka, applies score-monotonicity checks, and writes to the Redis Sorted Set Cluster using ZADD NX and conditional Lua scripts. Redis sorted sets are the beating heart of this design: each member is a player ID, each score is the player’s tournament score, and Redis maintains the ordering automatically in O(log N) time per write.
On the read path, the Top-N API serves /leaderboard/top100 requests backed by a TTL-cached ZRANGE result. The cache sits behind a CDN layer that absorbs read amplification from millions of concurrent spectators. The Personal Rank Service answers individual rank queries by calling ZRANK against the Redis cluster and optionally approximating rank using a partition-merge strategy when the dataset is sharded.
Redis sorted sets give us O(log N) writes and O(log N) rank queries natively. For 10 million members, log2(10M) is about 23 comparisons - fast enough that a single Redis node can sustain over 100,000 sorted set operations per second. The entire leaderboard fits in memory at roughly 400MB. This is the insight that makes the design tractable.
Component Deep Dives
Premium Content
Unlock the full article along with everything else in the archive — all in one place.