The Blog

Jan 2
0

Creating high score tables (leaderboards) using Redis

by David Czarnecki

Categories

To my other colleagues at Agora Games, I have one word: FIRST!

Now that I’ve got that out of my system, down to the business at hand. I’ve read a number of articles on Redis that mention how it could be used for high score tables (leaderboards), but I didn’t see any examples that would walk you through exactly how to do that. Time to rectify that since it’s 2011 and we still don’t have flying cars … so ,,|, future.

UPDATE: Please use the original post for any comments/feedback. This post is a placeholder.

UPDATE: All of this has been packaged up in the leaderboard gem on GitHub.

Leaderboards are a small, but important part of the engineering we do at Agora Games. As such, when not riding the short bus, I’m looking for ways to improve leaderboard generation for various situations. So, here’s how I might approach creating leaderboards using Redis.

I offer the following gist. Here’s the jist of the gist:

* The following was prototyped using the redis CLI. You can obviously port the logic to your favorite language binding for Redis, *cough* Ruby *cough*.

* You will be using the Sorted Set commands in Redis.

* You will need to be running at least Redis 2.1.6 to use the ZREVRANGEBYSCORE method.

* Add players to the HIGHSCORES “table” using the ZADD method.

* Print out the players (with a given page size) from the HIGHSCORES “table” using the ZREVRANGEBYSCORE method to show scores from highest to lowest.

* There is some bookkeeping code that you would do at the application level in terms of tracking what page you are on, dividing the total # of leaderboard entries into pages for a given page size, etc.

* Create an “Around Me” leaderboard with scores of individuals above and below an individual player using a combination of the ZREVRANK and ZREVRANGEBYSCORE methods.

* Again, there is some bookkeeping code that you would do at the application level in terms of tracking how many players above or below to show and taking into account offsets.

* The data that you store for a given player could be richer than just the player name or player ID. This is probably a larger design consideration if you are considering the “Around Me” leaderboard situation.

NOTE: You will need at least Redis 2.1.6 to use the ZREVRANGEBYSCORE method.

Add players to HIGHSCORES table:

fossil:~ dczarnecki$ redis-cli
redis> zadd HIGHSCORES 1 player_1
(integer) 1
redis> zadd HIGHSCORES 2 player_2
(integer) 1
redis> zadd HIGHSCORES 3 player_3
(integer) 1
redis> zadd HIGHSCORES 4 player_4
(integer) 1
redis> zadd HIGHSCORES 4 player_5
(integer) 1
redis> zadd HIGHSCORES 6 player_6
(integer) 1
redis> zadd HIGHSCORES 7 player_7
(integer) 1
redis> zadd HIGHSCORES 8 player_8
(integer) 1
redis> zadd HIGHSCORES 9 player_9
(integer) 1
redis> zadd HIGHSCORES 10 player_10
(integer) 1

Find out how many players are in the HIGHSCORES table:

redis> zcard HIGHSCORES
(integer) 10

Print out all the players that are in the HIGHSCORES table:

redis> zrangebyscore HIGHSCORES -inf +inf WITHSCORES
1. "player_1"
2. "1"
3. "player_2"
4. "2"
5. "player_3"
6. "3"
7. "player_4"
8. "4"
9. "player_5"
10. "4"
11. "player_6"
12. "6"
13. "player_7"
14. "7"
15. "player_8"
16. "8"
17. "player_9"
18. "9"
19. "player_10"
20. "10"

Print out the 1st page of the HIGHSCORES table:

redis> zrangebyscore HIGHSCORES -inf +inf WITHSCORES LIMIT 0 5
1) "player_1"
2) "1"
3) "player_2"
4) "2"
5) "player_3"
6) "3"
7) "player_4"
8) "4"
9) "player_5"
10) "4"

Print out the 2nd page of the HIGHSCORES table:

redis> zrangebyscore HIGHSCORES -inf +inf WITHSCORES LIMIT 5 -1
1) "player_6"
2) "6"
3) "player_7"
4) "7"
5) "player_8"
6) "8"
7) "player_9"
8) "9"
9) "player_10"
10) "10"

Print out all the players that are in the HIGHSCORES table from highest score to lowest score:

redis> zrevrangebyscore HIGHSCORES +inf -inf WITHSCORES
1) "player_10"
2) "10"
3) "player_9"
4) "9"
5) "player_8"
6) "8"
7) "player_7"
8) "7"
9) "player_6"
10) "6"
11) "player_5"
12) "4"
13) "player_4"
14) "4"
15) "player_3"
16) "3"
17) "player_2"
18) "2"
19) "player_1"
20) "1"

Print out the 1st page of players that are in the HIGHSCORES table from highest score to lowest score:

redis> zrevrangebyscore HIGHSCORES +inf -inf WITHSCORES LIMIT 0 5
1) "player_10"
2) "10"
3) "player_9"
4) "9"
5) "player_8"
6) "8"
7) "player_7"
8) "7"
9) "player_6"
10) "6"

Print out the 2nd page of players that are in the HIGHSCORES table from highest score to lowest score:

redis> zrevrangebyscore HIGHSCORES +inf -inf WITHSCORES LIMIT 5 -1
1) "player_5"
2) "4"
3) "player_4"
4) "4"
5) "player_3"
6) "3"
7) "player_2"
8) "2"
9) "player_1"
10) "1"

Create an "Around Me" leaderboard with scores of individuals above and below me:

Find rank in reverse order:

redis> zrevrank HIGHSCORES player_6
(integer) 5

Get players above a given player:

redis> zrevrangebyscore HIGHSCORES +inf -inf WITHSCORES LIMIT 2 3
1) "player_9"
2) "9"
3) "player_8"
4) "8"
5) "player_7"
6) "7"

Get players below a given player:

redis> zrevrangebyscore HIGHSCORES +inf -inf WITHSCORES LIMIT 6 3
1) "player_5"
2) "4"
3) "player_4"
4) "4"
5) "player_3"
6) "3"

Another "Around Me" leaderboard:

redis> zrevrank HIGHSCORES player_4
(integer) 7

redis> zrevrangebyscore HIGHSCORES +inf -inf WITHSCORES LIMIT 4 3
1) "player_7"
2) "7"
3) "player_6"
4) "6"
5) "player_5"
6) "4"

redis> zrevrangebyscore HIGHSCORES +inf -inf WITHSCORES LIMIT 8 3
1) "player_3"
2) "3"
3) "player_2"
4) "2"
5) "player_1"
6) "1"

Actually, let's simplify the "Around Me" leaderboard to just be 2 calls and include the individual in the leaderboard results:

  redis> zrevrank HIGHSCORES player_6
  (integer) 4
  
  redis> zrevrangebyscore HIGHSCORES +inf -inf WITHSCORES LIMIT 1 7
  1. "player_9"
  2. "9"
  3. "player_8"
  4. "8"
  5. "player_7"
  6. "7"
  7. "player_6"
  8. "6"
  9. "player_5"
  10. "4"
  11. "player_4"
  12. "4"
  13. "player_3"
  14. "3"

view raw gistfile1.txt This Gist brought to you by GitHub.

UPDATE: 1/3/2011: I updated the gist to do the “Around Me” leaderboard in 2 calls.

You can find more hilarity over on my Twitter account, CzarneckiD.

  • Reddit
  • Digg
  • del.icio.us
  • Facebook
  • Tumblr
  • Twitter