social
Game Leaderboard
Who’s at the top?
Per-game high scores, ranked with a secondary index on score.
Scores live under the game partition, one item per player, and a GSI re-keys them by negated score so the top of the leaderboard is a single Query on the index, highest first. A top ten is the same Query with a Limit.
The model
Score
A player's best score in a game.
- pk
- GAME#<gameId>
- sk
- PLAYER#<playerId>
Attributes: gameId (S), playerId (S), player (S), score (N), updatedAt (S), country (S), level (N)
Access patterns
- Query · GSI1Top scores for a game
List the highest scores in a game, ranked.
- GetItemGet a player's score
Fetch one player's score in a game.
- Query · GSI1Top N players in a game
The N highest scores in a game: a Query on the index stopped after N items. GSI1SK holds the negated score, so the index's natural ascending order is already highest first.
- ScanPlayer's rank across games
Every score one player holds, across games. A Scan reads the whole table to find them, and a filter on the player only trims what comes back, not what is read, so the run here returns every row it read. With a known list of games, a BatchGetItem on each game's key for the player reads only their rows.
Design notes
A GSI to rank, not to look upwhy
The base table answers get-a-player's-score by key. Ranking needs a different sort order, so a GSI re-keys every score under one partition per game, and the whole leaderboard is a single Query on the index.
Negate the score to rank descendinghow
A Query reads a partition in ascending sort-key order unless you pass ScanIndexForward false. Storing the negated score gets the same ranking from the default read, so every caller lands on the leaderboard order without having to remember the flag. The real, positive score stays in its own attribute for display.
Score as the index sort keywhy
DynamoDB returns a Query in sort-key order. With the negated score as GSI1SK, a top N is one Query on the index with a Limit: no scan, and no sorting in the application.
ALL projection, or INCLUDEtrade-off
GSI1 projects every attribute, so a leaderboard row needs no second read. Projecting only what a row shows - player, score, country and level - with INCLUDE would cut the index's storage and write amplification, and a GetItem on the base table, with the key the index already returns, fetches anything else.
The game as the partition keywhy
Each game is its own partition, in the table and in the index, so a top-N read touches one partition. That scales to many games without a hot partition, as long as no single game's writes outgrow what one partition can take.
A larger seed on purposewhy
Applied in full, the record pads Score to 1,000 rows, enough for a game's leaderboard to page through with Limit and LastEvaluatedKey; this page shows the hand-written rows only. A template hint writes each padded row's GSI1SK from that row's own score, so the padded rows rank where their scores put them.
Taught in the course
- The key is the address - GetItem needs the whole primary key: partition and sort.
- The sort key is a query language - Conditions on the sort key narrow a Query before it reads, for free.
- A different key for a different question - A secondary index re-keys your items so you can Query them on a new axis.
List the highest scores in a game, ranked.
Choose an access pattern above, or build your own request. See what comes back and what it costs.
Write transactions, streams, tags and TTL are among the operations this browser build leaves out. dynoxide's native build has them.