Design a Real-Time Gaming Leaderboard
1. Problem Statement & Scope Clarification
System Mission
Design an ultra-low-latency, globally distributed real-time gaming leaderboard platform (similar to Xbox Live, PlayStation Network, and competitive esports platforms) capable of processing hundreds of thousands of score updates per second, maintaining exact global rankings across tens of millions of active players, and delivering Top- and surrounding relative rank windows in under 5 milliseconds.
Functional Requirements
- Score Submission (
SubmitScore): Ingest and atomically update a player's score for a specific game and season, recalculating their exact global rank in real time. - Top- Global Leaderboard (
GetTopRankings): Retrieve the current top players (e.g., Top 100) with ranks, usernames, avatars, verified scores, and regional badges. - Surrounding Player Window (
GetSurroundingRanks): Retrieve a specific player's exact rank along with players directly above and below them (relative ranking window). - Deterministic Tie-Breaking: Automatically break score ties deterministically by awarding the higher rank to the player who achieved the score earlier in time.
- Periodic & Seasonal Reset: Support daily, weekly, and seasonal leaderboard archival and zero-downtime rollover.
- Live Top-K Push Broadcast: Stream live Top-100 rank changes over WebSockets to spectators during global live tournament matches.
Non-Functional Requirements (SLAs & SLOs)
- High Availability: uptime SLA for leaderboard query and score ingest endpoints.
- Ultra-Low Latency:
- Read Latency (Top- and Relative Window): P95 , P99 .
- Write Latency (Score Ingest & Rank Calculation): P95 , P99 .
- Throughput: Support and during global live gaming events.
- Accuracy: linearizable rank precision for top-tier competitive play (zero stale, inverted, or skipped ranks).
- Durability: Zero match score loss guarantee; all game events durably archived in an audit lake.
2. Capacity & Scale Estimation (Back-of-the-Envelope Math)
Player Base & Ingest QPS
- Total Registered Players: ().
- Daily Active Players (DAU): ().
- Daily Match Activity: Each DAU completes an average of matches/day:
- Average Ingest QPS:
- Peak Ingest QPS ( spike during global esports tournaments):
- Peak Read QPS (Top- views from game lobby screens):
Memory Footprint Derivation (Redis Sorted Set)
A Redis ZSET is implemented as a dual-structure: an in-memory SkipList (for range scans and rank lookups) combined with a Hash Table (for player-to-score point lookups):
- Member ID (
usr_998124): - Score (
float64IEEE-754 double precision): - SkipList Node pointers (level spans, forward/backward pointers):
- Hash Table dictionary entry (
dictEntrystruct): - Jemalloc memory alignment & allocator padding:
- Total In-Memory Size per Player Record: .
Accounting for 10 active game modes and seasons:
Adding jemalloc fragmentation and replication-buffer overhead ():
Every replica holds a full copy of the dataset, so this is the size of each node, not the total. Provision an Amazon ElastiCache Redis replication group of primary read replicas on cache.r6g.4xlarge ( per node, one node per AZ), leaving headroom for fragmentation spikes (Section 8.1). Three cache.r6g.xlarge nodes ( each) would not fit even one copy. If a single game mode alone outgrows one node, switch to cluster mode and hash-slot the lb:<game>:<season> keys across shards, since each ZSET must still live entirely on one shard.
Network Bandwidth
- Ingress Bandwidth (Score Updates):
- Egress Bandwidth (Top-100 Reads): Each Top-100 response returns 100 hydrated player cards ( JSON payload): Placing Amazon CloudFront in front of the Top-100 endpoint with a 2-second edge cache TTL absorbs of lobby polling traffic, reducing backend egress to .
Historical Storage Footprint (5-Year Horizon)
- Daily Match Audit Storage:
- 5-Year Historical Match Lake:
Streamed via Amazon Kinesis Data Firehose into an Amazon S3 Analytics Lake partitioned by
year/month/day/game_id.
3. High-Level Architecture & AWS Component Mapping
Synthesizing vector architecture diagram...
Follow the three kinds of traffic. Score writes: players reach the API fleet through the NLB, and each new score is a ZADD on the Redis primary, where a sorted set keeps ranks updated in O(log N). Rank reads: "what is my rank?" (ZREVRANK) and "show the top N" (ZREVRANGE) go to read replicas, so heavy reads never slow down writes. Spectators: the top 100 is cached at CloudFront for 2 s, and changes are pushed live over WebSocket from the primary's Pub/Sub. Every score is also written to Kinesis; in the "Durable Match Audit & Historical Archive" panel, it is archived to S3 and checked by an anti-cheat worker. Redis gives speed, the stream gives durability and auditing, and caching absorbs the spectator crowd.
Data Flow Walkthrough
- Score Ingestion & Deterministic Tie-Breaking:
- Game server completes a match and emits
POST /v1/scorescontaining player ID, raw score, and HMAC-SHA256 match signature. - The Leaderboard API validates the token, applies the season-relative tie-breaking formula (Section 4.1) to encode submission time into the fractional part of the floating-point score, and executes an atomic
ZADD ... GTagainst the ElastiCache Redis Primary. - Concurrently, the API writes the raw match event to Amazon Kinesis Data Streams for durable auditing and anti-cheat verification.
- Game server completes a match and emits
- Top- Retrieval & Edge Offloading:
- Game clients request
GET /v1/leaderboards/{game_id}/top?limit=100. - The request hits Amazon CloudFront Edge Cache (TTL: 2 seconds). On edge miss, the request forwards to an ECS API task, which executes
ZREVRANGE lb:<game>:<season> 0 99 WITHSCORESagainst an ElastiCache Read Replica (). - The API batch-hydrates player display names and avatar URLs from Amazon DynamoDB (or a Redis Hash cache), strips the fractional tie-breaker bits, and returns the response.
- Game clients request
- Surrounding Window Calculation:
- Player queries
GET /v1/leaderboards/{game_id}/surrounding?radius=5. - The API executes
ZREVRANKto locate the player's exact 0-indexed rank , followed immediately byZREVRANGE (R - 5) (R + 5) WITHSCORES.
- Player queries
- Live Tournament Push Broadcast:
- If an update changes the Top-100 standings, the API publishes a lightweight delta event to a Redis Pub/Sub channel.
- The WebSocket Push Gateway fans out the new Top-100 snapshot to subscribed spectator clients in real time ().
Concrete Step-by-Step Request Walkthrough: Tracing Score Submission & Rank Calculation
| Step # | Event / Action | Component State | Distributed Transition | Output / Response |
|---|---|---|---|---|
| 1 | Match completes; client submits score | Match verification token signed by game server | Client calls POST /v1/scores with payload and HMAC signature | Request reaches ECS API task via NLB |
| 2 | HMAC token verification | In-memory cryptographic secret validation | API verifies HMAC_SHA256(user_id + score + match_id, secret) | Authentication succeeds in ; spoofed scores rejected (HTTP 403) |
| 3 | Tie-breaking score calculation | Local CPU floating-point encoding | Season-relative offset: since season start (Section 4.1) | Deterministic combined float64 score generated with zero mantissa loss |
| 4 | Atomic Redis score commit | TCP socket to Redis Primary | API executes ZADD lb:game_01:s12 GT 50120.99988 usr_998124 | Redis updates SkipList and Hash Table in |
| 5 | Instant global rank resolution | Redis in-memory lookup | API executes ZREVRANK lb:game_01:s12 usr_998124 | Returns exact rank (e.g., Rank 42) in |
| 6 | Asynchronous audit log dispatch | Non-blocking Kinesis client pool | PutRecord writes raw match event to Kinesis Data Streams | Match event logged for anti-cheat audit; P99 response returned in |
4. API Interface Design & Wire Protocols
1. Deterministic Tie-Breaking Score Math & Precision Boundary Analysis
By default, Redis ZSET orders members with identical scores lexicographically by user_id. In competitive esports, identical scores must break ties by awarding the higher rank to the player who achieved the score earlier in time.
IEEE-754 Precision Analysis & The Precision Collapse Trap
A standard Redis score is an IEEE-754 double-precision 64-bit float (float64), possessing 53 bits of significand (mantissa), providing approximately decimal digits of precision ().
If a system naively adds a 13-digit Unix millisecond timestamp () to a score of (6 decimal digits), total significant digits required exceed 19 decimal digits, triggering catastrophic floating-point mantissa truncation: the low-order timestamp bits are rounded off, and two scores achieved minutes apart collapse to identical float values!
Floating-Point Mantissa Truncation (): In IEEE-754 double precision (float64), scores possess strictly 53 bits of mantissa ( decimal digits). Adding a standard 13-digit Unix millisecond timestamp () to an 8-digit score exceeds 21 decimal digits, permanently rounding off low-order timestamp bits and collapsing distinct submission times. Always use tournament-relative second offsets () to stay safely within 15 decimal digits.
Production Relative-Offset Formula
To preserve exact millisecond precision without exceeding 15 decimal digits, production systems encode a tournament-relative second offset ():
- Maximum season duration: 30 days (7 decimal digits).
- Maximum raw score: (8 decimal digits).
- Total required precision: (zero truncation).
Because increases monotonically as the tournament progresses, an earlier timestamp yields a larger fractional value, guaranteeing deterministic first-achiever precedence:
textPlayer A (scored at T_rel = 1000s): 50000 + (1.0 - 0.0001000) = 50000.9999000 Player B (scored at T_rel = 2000s): 50000 + (1.0 - 0.0002000) = 50000.9998000 Result: Score A > Score B -> Player A is ranked higher than Player B
64-Bit Integer Bit-Shifting Alternative (Internal Leaderboards)
When implementing custom leaderboard engines or using integer secondary indices, the score and timestamp are packed into a single 64-bit unsigned integer:
The upper 32 bits store the raw score (up to ), and the lower 32 bits store the inverted timestamp (up to 136 years of relative seconds), yielding bitwise-deterministic ordering with zero floating-point rounding hazards.
Upon read, the API displays the clean integer score:
2. RESTful Score Submission Endpoint
httpPOST /v1/scores Host: api.leaderboard.aws.internal Authorization: Bearer <jwt_session_token> X-Match-Token: 3a7b9c1d4e8f0a2b4c6e8d0f2a4b6c8e Content-Type: application/json { "game_id": "game_01", "season_id": "season_12", "user_id": "usr_998124", "raw_score": 50120, "match_id": "match_881923" }
Response: 200 OK
json{ "status": "SUCCESS", "data": { "user_id": "usr_998124", "global_rank": 42, "score": 50120, "is_new_high_score": true, "previous_rank": 78 } }
3. RESTful Top-100 Rankings Endpoint
httpGET /v1/leaderboards/game_01/top?season_id=season_12&limit=100 Host: api.leaderboard.aws.internal Accept: application/json Response: 200 OK Cache-Control: public, max-age=2, stale-while-revalidate=5 { "status": "SUCCESS", "data": { "game_id": "game_01", "season_id": "season_12", "total_players": 48290124, "entries": [ { "rank": 1, "user_id": "usr_apex_pro", "username": "Valkyrie_Queen", "avatar_url": "https://cdn.gaming.internal/avatars/valk.webp", "score": 98450, "country_code": "SE" }, { "rank": 2, "user_id": "usr_titan_99", "username": "IronClad", "avatar_url": "https://cdn.gaming.internal/avatars/iron.webp", "score": 97120, "country_code": "KR" } ] } }
4. gRPC Interface Specification (leaderboard.proto)
protobufsyntax = "proto3"; package hispeeddesign.leaderboard.v1; service LeaderboardService { rpc SubmitScore (SubmitScoreRequest) returns (SubmitScoreResponse); rpc GetTopRankings (GetTopRankingsRequest) returns (GetTopRankingsResponse); rpc GetSurroundingRanks (GetSurroundingRanksRequest) returns (GetSurroundingRanksResponse); } message SubmitScoreRequest { string game_id = 1; string season_id = 2; string user_id = 3; double raw_score = 4; string match_verification_token = 5; } message SubmitScoreResponse { int64 global_rank = 1; double verified_score = 2; bool is_new_high_score = 3; } message GetTopRankingsRequest { string game_id = 1; string season_id = 2; int32 top_n = 3; } message LeaderboardEntry { int64 rank = 1; string user_id = 2; string username = 3; string avatar_url = 4; double score = 5; } message GetTopRankingsResponse { repeated LeaderboardEntry entries = 1; int64 total_participants = 2; int64 last_updated_epoch_ms = 3; } message GetSurroundingRanksRequest { string game_id = 1; string season_id = 2; string user_id = 3; int32 radius = 4; } message GetSurroundingRanksResponse { int64 user_rank = 1; repeated LeaderboardEntry surrounding_entries = 2; }
Unlock Complete Architecture & Production Runbooks
You have explored the free architectural preview (~44%). Spend 1 Coin to unlock the remaining 7 production deep-dive sections for a full 24 hours.