Gaming Leaderboard System Design Template
Introductory: sorted sets, time-encoded tie breaking within 53 bits, top N and my rank, sharded ranks, friends boards, seasonal resets, anti-cheat boundary
使用方法: Use it when adding live rankings to a game or practising an introductory system design interview. Fill the ranking scope and the tie rule first, then the player count per board. The main pitfall is keeping scores only in the cache and accepting scores from game clients instead of trusted servers.
预览
System Design: [Gaming leaderboard name]
Reference design for a real-time gaming leaderboard: score updates from trusted game servers, ranking with in-memory sorted sets, deterministic tie breaking, "my rank" and "top N" queries, sharding when one set is not enough, friends leaderboards, seasonal resets, and the boundary with anti-cheat. It is an introductory design: it explains each building block before using it, so it suits a first system design review or interview practice. It follows the structure of the System Design Document template, adds a 45-minute interview path, access-pattern and failure-mode tables, and is pre-filled with a worked design to adapt. Replace every
[bracketed]value and every row marked Example; values marked Example are assumptions, not benchmarks.
Document control
| Field | Value |
|---|---|
| Document ID | [SDD-LB-001] |
| System / feature | [Real-time leaderboard] |
| Version | [0.1] |
| Status | [Draft / In review / Approved / Implemented / Superseded] |
| Owner | [@owner] |
| Author(s) | [@author] |
| Reviewers | [@reviewer-1], [@reviewer-2] |
| Last updated | YYYY-MM-DD |
| Classification | [Internal] |
| Difficulty | Introductory |
| Games and modes covered | [Game titles, modes, regions] |
| Related | [Match service design], [Anti-cheat policy], [ADR: tie rule] |
Approvals
| Role | Name | Decision | Date |
|---|---|---|---|
| Tech lead | [@name] | [Approved / Changes requested] | YYYY-MM-DD |
| Game design (ranking rules) | [@name] | [Decision] | YYYY-MM-DD |
| SRE / operations | [@name] | [Decision] | YYYY-MM-DD |
| Trust and safety (anti-cheat) | [@name] | [Decision] | YYYY-MM-DD |
Revision history
| Version | Date | Author | Change summary |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [@name] | First draft for design review |
| 0.2 | YYYY-MM-DD | [@name] | Example: Encoded time into the score to break ties by first to reach |
| 0.3 | YYYY-MM-DD | [@name] | Example: Added friends leaderboard computed at read time |
| 1.0 | YYYY-MM-DD | [@name] | Example: Approved |
Contents
1. Overview
1.1 Problem statement
[Example: The game computes rankings with a nightly SQL job that sorts all players by points. Players see yesterday's rank, the "top 100" page times out during events, and two players with equal points swap places between page loads. The new season mode needs live ranks for 50 million players.]
1.2 Proposed solution
[Example: When a match ends, the match service (a trusted server, not the game client) publishes a signed score event. A leaderboard writer records the event durably in a database and updates an in-memory sorted set for the current season. Ranks and the top 100 are read from the sorted set in a few milliseconds. Ties are broken by who reached the score first, encoded inside the sorted-set score. Friends leaderboards are computed at read time from the friend list. Each season uses a new key, so a reset is a key switch, not a mass delete.]
1.3 Decisions requested from reviewers
| ID | Question | Recommended option | Section |
|---|---|---|---|
| Q-01 | Example: Database ORDER BY or in-memory sorted set? | Sorted set, database as source of truth | 7.1, 10 |
| Q-02 | Example: How are ties broken? | First to reach the score ranks higher | 7.2 |
| Q-03 | Example: Shard now or later? | One primary with replicas now; shard plan ready | 7.3 |
| Q-04 | Example: Who can submit scores? | Only the match service, with signed events | 7.6 |
| [Q-nn] | [Question] | [Option] | [Section] |
1.4 45-minute interview path
Use this order when the document serves an interview or a short review.
| Minutes | Cover | Output on the whiteboard |
|---|---|---|
| 0 to 5 | Scope: update score, top N, my rank, friends; season reset; below-the-line items | Four operations |
| 5 to 10 | Estimates: updates per second, players per board, memory | Three numbers, "fits in one node" consequence |
| 10 to 15 | API: submit score (server only), get top, get my rank with neighbours | Three calls |
| 15 to 25 | High-level design: match service, writer, database, sorted set, read API | One diagram |
| 25 to 40 | One deep dive: why a database sort fails and how a sorted set works (default), or ties, or sharding | Options, choice, reason |
| 40 to 45 | Failures (cache lost, cheating), 10x change | Failure rows |
Start the deep dive with the naive answer, a database query that counts rows with a higher score, and show why it gets slower as players grow. Then introduce the sorted set and its logarithmic rank lookup.
2. Requirements
2.1 Core functional requirements
| ID | Requirement | Priority |
|---|---|---|
| FR-01 | Example: Add points to a player's season score when a match ends | Must |
| FR-02 | Example: Show the top 100 players of the season | Must |
| FR-03 | Example: Show a player's rank and the 5 players above and below | Must |
| FR-04 | Example: Break ties deterministically: first to reach the score ranks higher | Must |
| FR-05 | Example: Start a new season monthly, keep past seasons readable | Must |
| FR-06 | Example: Show a leaderboard of the player's friends | Should |
| FR-07 | Example: Remove a banned player and restore ranks | Must |
| [FR-nn] | [Requirement] | [Must / Should / Could] |
2.2 Non-functional requirements
| ID | Quality | Target | Measured as |
|---|---|---|---|
| NFR-01 | Read latency | Example: p99 at or below 50 ms for top 100 and my rank | API timing |
| NFR-02 | Freshness | Example: New score reflected in rank within 2 s of match end | Event timestamp to read |
| NFR-03 | Durability | Example: No accepted score lost, even if the cache is lost | Rebuild drill |
| NFR-04 | Availability | Example: 99.9% for reads; writes may queue during incidents | Probes |
| NFR-05 | Correctness | Example: Same rank shown to every viewer at the same moment | Consistency test |
| [NFR-nn] | [Quality] | [Target] | [Measurement] |
2.3 Below the line (out of scope)
- [Example: matchmaking ratings such as Elo or Glicko (a separate service; the leaderboard can display them)]
- [Example: cheat detection itself (anti-cheat system; see 7.6 for the boundary)]
- [Example: rewards and prize payout at season end]
- [Example: cross-game global rankings]
- [Item deferred, with the release that will take it]
3. Capacity estimation
3.1 Assumptions
| Input | Symbol | Value | Source |
|---|---|---|---|
| Daily active players | Example: 25 million | Analytics | |
| Matches per player per day | Example: 10 | Analytics | |
| Leaderboard views per player per day | Example: 20 | Client telemetry | |
| Players in one season board | Example: 50 million | Monthly actives | |
| Memory per sorted-set entry including overhead | Example: 100 bytes | Measured with 16-byte ids | |
| Peak to average ratio (events, weekends) | Example: 5 | Traffic history | |
| Simple operations one cache node serves per second | Example: 100 000 | Benchmark on target instance |
3.2 Score updates
Consequence: the write rate is far below what one in-memory node handles, so sharding is not needed for writes. Writes go through a queue only to absorb spikes and to make retries safe.
3.3 Reads
The top 100 is the same for everyone, so it is cached in the API for 1 s. "My rank" is per player and goes to the sorted set. Consequence: about operations per second at peak, under half of one node's Example capacity ; read replicas add headroom and availability.
3.4 Memory
Consequence: the current season fits on one node with room to spare. Past seasons move to the database and are not kept in memory.
3.5 Summary
| Quantity | Estimate | Design consequence |
|---|---|---|
| Score updates | ~14 500/s at peak | One primary; queue for spikes and retries |
| Reads | ~29 000/s at peak | Cache top 100 for 1 s; replicas for my-rank |
| Memory | ~5 GB per season board | One node; shard only for much larger boards (7.3) |
| Durability | Cache can be lost | Database is the source of truth; rebuild job |
4. API design
4.1 Endpoints
| Interface | Operation | Caller | Purpose |
|---|---|---|---|
| Internal event | ScoreAwarded{match_id, player_id, points, season, ts, signature} | Match service only | Add points |
| Public API | GET /v1/leaderboards/{board}/top?limit=100 | Client | Top N |
| Public API | GET /v1/leaderboards/{board}/players/{player_id}?around=5 | Client | Rank and neighbours |
| Public API | GET /v1/leaderboards/{board}/friends | Client (signed in) | Friends ranking |
| Admin | POST /v1/leaderboards/{board}/players/{player_id}/remove | Trust and safety | Remove a banned player |
4.2 Request and response examples
A rank request for one player with neighbours, and the response:
GET /v1/leaderboards/season-2026-10/players/p_8812?around=2 HTTP/1.1
{"board": "season-2026-10", "player": {"player_id": "p_8812", "rank": 1042, "points": 18350}, "neighbours": [{"rank": 1040, "player_id": "p_1201", "points": 18360}, {"rank": 1041, "player_id": "p_5530", "points": 18350}, {"rank": 1043, "player_id": "p_0077", "points": 18340}, {"rank": 1044, "player_id": "p_3109", "points": 18335}], "as_of": "2026-10-22T09:30:01Z"}
Players p_5530 and p_8812 both have 18 350 points; p_5530 reached it first, so it ranks higher (7.2).
4.3 Errors and idempotency
| Case | Status | Body type |
|---|---|---|
| Unknown board or season | 404 | problems/unknown-board |
| Player has no score this season | 200 with rank: null | Not an error |
| Score event with bad signature | Rejected, logged for trust and safety | Not visible to clients |
| Too many requests | 429 with Retry-After | problems/rate-limited |
Score events carry match_id. The writer records (season, match_id, player_id) with a unique constraint, so a replayed event adds points once.
5. Data model
5.1 Entities
The database holds durable records; the sorted set is a derived index.
5.2 Storage choices
| Data | Store | Key | Why |
|---|---|---|---|
| Score events | Example: Relational database, partitioned by season | (season_id, match_id, player_id) | Idempotency and audit |
| Player totals | Same database | (season_id, player_id) | Source for rebuild and season archive |
| Live ranking | Example: Redis sorted set | lb:{game}:{season} | Rank and range in logarithmic time |
| Final standings | Database table per season | (season_id, final_rank) | Past seasons without memory cost |
| Friend lists | Social service | player_id | Owned elsewhere, read at query time |
5.3 Access patterns
| Query | Key or index that serves it | Partition key and rationale |
|---|---|---|
| Top 100 of the season | ZRANGE lb:{game}:{season} 0 99 REV | Board key; one board fits one node |
| My rank | ZREVRANK on the board key with the player id | Same board key |
| Players around me | ZRANGE ... REV from rank minus 5 to rank plus 5 | Same board key |
| Friends ranking | ZMSCORE for up to a few hundred friend ids, sort in the API | Board key; friends are a small set read at query time |
| Rebuild after loss | Scan PLAYER_SCORE by season_id | Season partition; sequential read |
| Past season rank | FINAL_STANDING by (season_id, player_id) | Season partition; cold data |
6. High-level design
| Component | Responsibility | Scales by |
|---|---|---|
| Match service | Decide results and points on the server | Game fleet |
| Score event queue | Buffer spikes, retry safely | Partitions by player id |
| Leaderboard writer | Verify signature, record event, update totals and sorted set | Instances, one partition each |
| Sorted set primary and replicas | Rank and range queries | Replicas; shards in 7.3 |
| Leaderboard API | Read paths, top-N cache, friends merge | Stateless instances |
| Rebuild job | Recreate the sorted set from the database | On demand |
7. Detailed design
7.1 Deep dive: why a sorted set
To find a player's rank, something must know how many players have a higher score.
| Option | Top N | My rank | Problem |
|---|---|---|---|
Database ORDER BY points DESC LIMIT 100 | Fast with an index | COUNT(*) WHERE points > mine scans many index entries | Rank cost grows with the number of players above you |
| Batch job that writes ranks nightly | Fast | Fast | Hours old |
| In-memory sorted set (skip list plus hash map) | Logarithmic | Logarithmic | Memory bound; needs a durable source |
A sorted set keeps members ordered by a numeric score. Adding or updating a
member and looking up its rank both take time proportional to the logarithm
of the set size, so 50 million players cost only a few dozen steps. In Redis
the commands are ZADD or ZINCRBY to write, ZREVRANK for rank from the
top, and ZRANGE ... REV for a slice by rank (ZREVRANGE is deprecated since Redis 6.2).
Choice: a sorted set per board, with the database as the source of truth. Reason: it is the only option that serves both top N and my rank in milliseconds for a live board.
The writer writes the absolute total computed by the database rather than incrementing in the cache. A retried write then sets the same value again instead of adding points twice.
7.2 Deep dive: ties
When scores are equal, Redis orders members by their names byte by byte
(reversed for the ZREV commands). The order of two tied players then
depends on their ids, which is not a game rule.
| Option | Rule | Cost |
|---|---|---|
| Accept lexicographic order | Arbitrary but stable | Players see unfair order |
| Shared rank ("both are 1041st") | Equal scores share a rank | Rank query must count distinct higher scores; more work |
| Encode time in the score | Earlier achiever ranks higher | Score range must fit the encoding |
Choice: encode time. Redis scores are double-precision numbers, which represent integers exactly only up to . Split those 53 bits into points and time: with the whole seconds since the season started,
seconds is about 48.5 days, enough for a monthly season, which leaves 31 bits for points (up to about 2.1 billion). Earlier gives a larger composite, so the earlier achiever ranks higher. The API decodes points as . Reason: it keeps every query a single sorted-set call. Players who reach the same score in the same second still fall back to id order; document that as the final rule.
7.3 Deep dive: sharding and top N
Sharding is not needed at the estimated size (3.5), but plan it for boards that outgrow one node, for example a global all-time board.
| Option | Top N | My rank | Risk |
|---|---|---|---|
| Shard by player id hash into sets | Read top N from each shard, merge results | across all shards | Rank needs calls |
| Shard by score range (buckets) | Read the highest bucket | Sizes of higher buckets plus rank inside own bucket | Hot top bucket; players move between shards |
| Exact rank for the top only, percentile for the rest | Exact for top 10 000 | "Top 3%" from a score histogram | Approximate for most players |
Choice: hash sharding when needed, plus percentile display below the top 10 000. Reason: hash sharding keeps load even and top N is a cheap merge; players outside the top rarely need an exact six-digit rank, and a histogram answers "top 3%" with one lookup.
7.4 Friends leaderboards
| Option | How | Cost |
|---|---|---|
| Fan out on write | Update a sorted set per friend on every score | Each update writes hundreds of sets |
| Compute on read | Fetch friend ids, read their scores with one multi-get, sort in the API | One social call plus one cache call per view |
Choose compute on read: friend lists are small (cap at Example 500), and friends boards are viewed far less often than scores change. Cache the result per player for Example 10 s.
7.5 Seasons and resets
Each season has its own key, for example lb:{game}:2026-10. At the season
boundary (defined in UTC), the writer switches to the new key; the old set
stays read-only for Example 7 days for late disputes, then a job writes
FINAL_STANDING and deletes the old key. No mass delete happens at
midnight, and late events for the old season are rejected by the season
check. All-time boards, if any, are separate keys updated in parallel.
7.6 Anti-cheat boundary
The leaderboard is not where cheating is detected, but its design can make cheating harder:
- Accept scores only from the match service, never from game clients, and verify an event signature or service identity.
- Reject events outside plausible ranges (for example more points than a match can award) and route them to review.
- Support removal: the anti-cheat system calls the admin API, the writer marks the player removed in the database and removes the member from the sorted set; ranks of everyone below change immediately.
- Keep every score event, so points can be recomputed after a rules change or an exploit.
7.7 How real systems do it
How real systems do it (paraphrased, not copied). The AWS Database Blog post "Build a real-time gaming leaderboard with Amazon ElastiCache for Redis" (2019, updated 2024) describes moving ranking out of a relational database into Redis sorted sets, so that each score write keeps the set ordered and rank queries no longer scan tables. A later AWS for Games post on Amazon MemoryDB discusses the durability gap of a cache-only leaderboard and the common pairing of an in-memory store with a persistent database, which is the split used in this design. The Redis documentation itself lists leaderboards as a typical use of sorted sets and states that equal scores are ordered lexicographically, which is why 7.2 exists. Read the originals in 12.3; this box keeps only the general idea.
8. Scalability and performance
| Concern | Approach | Limit before redesign |
|---|---|---|
| Score writes | One primary, queue in front | Example: about 50% of node capacity |
| Top-N reads | 1 s cache in the API | None in practice |
| My-rank reads | Read replicas | Replica lag above NFR-02 |
| Board size | One node per board | Example: above 30 GB, shard by player hash (7.3) |
| Events (launch day) | Queue absorbs bursts; writer scales with partitions | Queue lag above 2 s |
Latency budget for "my rank with neighbours" (target p99 50 ms):
| Step | Budget |
|---|---|
| API, auth | 10 ms |
ZREVRANK on a replica | 2 ms |
ZRANGE ... REV for neighbours | 2 ms |
| Profile names for 11 players (cached) | 10 ms |
| Network and headroom | 26 ms |
9. Reliability, security and operations
9.1 Failure modes
| Component | Failure | Detection | Blast radius | Mitigation | RTO |
|---|---|---|---|---|---|
| Sorted set primary | Node lost | Health check | Writes for the board pause | Promote replica; replay recent events from the queue | Example: 1 min |
| Sorted set data | Lost or corrupted | Count check against database | Board shows wrong ranks | Rebuild job from PLAYER_SCORE | Example: 15 min for 50 million entries |
| Writer | Crashes mid-batch | Queue lag | Freshness only | Idempotent events, absolute totals | Example: 1 min |
| Database | Primary failover | Error rate | Writes queue; reads unaffected | Queue holds events until it returns | Example: 5 min |
| Season switch | Wrong boundary or timezone | Events rejected spike | One season's scores | UTC boundary in config, rehearsed in staging | Example: 30 min |
| Exploit | Inflated scores | Anti-cheat signals, outlier alerts | Top of the board | Remove, recompute from events | Example: 1 h |
| [Component] | [Failure] | [Signal] | [Scope] | [Mitigation] | [Time] |
9.2 Security and privacy
| Threat | Control |
|---|---|
| Client submits its own score | No public write endpoint; only the match service publishes events |
| Forged events on the queue | Signed events or authenticated producer identity |
| Scraping of player lists | Rate limits; public names only, no account ids |
| Display of minors' names | Show display names chosen under the game's child safety rules |
| Abuse in display names | Name moderation before a player can appear in public boards |
| [Threat] | [Control] |
9.3 Observability
| Signal | Metric | Alert threshold |
|---|---|---|
| Freshness | Match end to rank update, p99 | Above 2 s for 10 min |
| Consistency | Sorted-set size vs database count | Difference above 0.01% |
| Read latency | p99 per endpoint | Above 50 ms |
| Suspicious scores | Points per match above the game maximum | Any |
9.4 Cost estimate
| Item | Unit cost | Quantity | Monthly cost |
|---|---|---|---|
| Cache nodes (primary and 2 replicas) | [USD per node-month] | 3 | [USD] |
| Database | [USD per month] | 1 cluster | [USD] |
| Queue | [USD per month] | [Partitions] | [USD] |
| Total | [USD] |
10. Trade-offs and alternatives
| Decision | Options | Chosen | Why | What we give up |
|---|---|---|---|---|
| Ranking store | Database sort; batch; sorted set | Sorted set plus database | Live ranks in milliseconds | Two stores to keep in sync |
| Tie rule | Lexicographic; shared rank; time encoded | Time encoded | Fair, one call per query | Score range limited to 31 bits of points |
| Write style | Increment in cache; set absolute total | Absolute total from database | Retries are safe | Database on the write path |
| Friends board | Fan out on write; compute on read | Compute on read | Small lists, rare views | Extra call per view |
| Season reset | Delete members; new key | New key | Instant, no mass delete | Old keys to clean up |
| What changes at 10x / 0.1x | 10x players: shard by player hash and show percentiles below the top. 0.1x: a database index with a 10 s cached top N is enough | Review per game launch | Size moves the cache from optional to required | [Note] |
| [Decision] | [Options] | [Chosen] | [Why] | [Cost] |
11. Delivery plan and risks
11.1 Milestones
| Milestone | Scope | Exit criterion | Target date |
|---|---|---|---|
| M1 | Writer, database, sorted set, top 100 | Load test at 2 times peak | YYYY-MM-DD |
| M2 | My rank, neighbours, tie rule | Tie tests pass | YYYY-MM-DD |
| M3 | Seasons and archive | Season switch rehearsed | YYYY-MM-DD |
| M4 | Friends board, removal flow | Removal reflected within 2 s | YYYY-MM-DD |
11.2 Rollout and rollback
[Example: Run the new leaderboard in shadow for one week, comparing ranks with the nightly job for a sample of players. Switch the client to the new API behind a flag; rollback switches the flag back to the nightly ranks.]
11.3 Risks
| ID | Risk | Likelihood | Impact | Mitigation | Owner |
|---|---|---|---|---|---|
| R-01 | Example: Points exceed 31 bits after a scoring change | Low | High | Validate in the writer; widen time unit or shorten season | [@name] |
| R-02 | Example: Players dispute tie order | Medium | Low | Publish the rule in game help | [@name] |
| R-03 | Example: Exploit floods the top before detection | Medium | High | Plausibility limits, fast removal tool | [@name] |
| [R-nn] | [Risk] | [L / M / H] | [L / M / H] | [Mitigation] | [@name] |
12. Appendices
12.1 Open questions
| ID | Question | Owner | Due |
|---|---|---|---|
| OQ-01 | Example: Regional boards as separate keys or filtered views? | [@name] | YYYY-MM-DD |
| OQ-02 | Example: Do removed players keep their final standing record? | [@name] | YYYY-MM-DD |
| [OQ-nn] | [Question] | [@name] | YYYY-MM-DD |
12.2 Glossary
| Term | Meaning |
|---|---|
| Sorted set | A collection of unique members ordered by a numeric score |
| Rank | Position of a player counted from the top, starting at 1 in the UI |
| Composite score | Points and time packed into one number so ties sort correctly |
| Season | A time-boxed competition with its own board |
| Percentile rank | Share of players with a lower score, shown instead of an exact rank |
12.3 References
| Reference | Use |
|---|---|
| Redis documentation, "Redis sorted sets" (redis.io) | 7.1 commands, 7.2 lexicographic tie order |
| Tan, "Build a real-time gaming leaderboard with Amazon ElastiCache for Redis", AWS Database Blog, 2019 (updated 2024) | 7.7 |
| Ishii, "Rethinking Game Leaderboards with Amazon MemoryDB for Redis", AWS for Games Blog, 2022 | 7.7 durability discussion |
MDN Web Docs, Number.MAX_SAFE_INTEGER | 7.2 exact integers up to 2 to the power 53 |
| Xu and Lam, System Design Interview: An Insider's Guide, Volume 2 (2022), chapter "Real-time Gaming Leaderboard" | Inspiration only; no text or figures reused |
Distributed Key-Value Store System Design (system-design/design-distributed-kv-store) | Replication background |
Back-of-Envelope Estimation and Capacity Model (system-design/back-of-envelope-estimation) | Method for section 3 |
Standard and licence note
Based on common industry practice for real-time ranking and on the public documentation cited in 12.3. The book and engineering write-ups listed there are inspiration only; no text, diagrams or numbers were copied, and all figures here are illustrative examples. Check the current documentation of your cache and database for command behaviour and limits. Original text written for this collection. Template licence: MIT.
相关模板
Zanzibar-Style Authorization Service System Design Template
Relationship-based authorization service: relation tuples, namespace config, Check/Expand/List/Watch APIs, consistency tokens, caching, sharding, failure modes
系统设计CDC Pipeline System Design Template
CDC pipeline: log-based capture, watermark chunked snapshot, per-key order, schema evolution, outbox, search, cache and warehouse sinks, replay
系统设计Chat System Design Template (Reference Design)
Real-time 1:1 and group chat: WebSocket gateways, per-conversation ordering, fan-out, receipts, offline sync, push, E2EE