Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Leaderboards - A Practical Use-Case for Redis

Leaderboards - A Practical Use-Case for Redis

In this talk, I'll present a practical use-case for Redis, leaderboards (aka scoreboards aka high score tables). The pain and frustration of having to engineer leaderboards using a traditional database had me constantly looking for a more practical, simpler and more scalable solution. I will cover the commands in Redis to support leaderboards, a high-level library wrapping the Redis commands, use-cases for leaderboards, performance metrics, and a very interesting developer thought experiment that arose from developing the library.

David Czarnecki

October 13, 2011
Tweet

More Decks by David Czarnecki

Other Decks in Programming

Transcript

  1. Leaderboard gem • Semantic wrapper around Redis sorted sets •

    Overall data • Total Members, Total Pages • Individual data • Rank, Find Rank, Find Score, Change Score, Check Member, Remove Member • Group data • Leaders, “Around Me”, Arbitrary List
  2. ruby-1.9.2-p180 :002 > highscore_lb = Leaderboard.new('highscores') => #<Leaderboard:0x0000010307b530 @leaderboard_name="highscores", @page_size=25,

    @redis_connection=#<Redis client v2.2.2 connected to redis:// localhost:6379/0 (Redis v2.2.5)>> Create a leaderboard
  3. ruby-1.9.2-p180 :010 > highscore_lb.score_for('member_4') => 4.0 ruby-1.9.2-p180 :011 > highscore_lb.rank_for('member_4')

    => 7 ruby-1.9.2-p180 :012 > highscore_lb.rank_for('member_10') => 1 Get some information about specific members
  4. ruby-1.9.2-p180 :013 > highscore_lb.leaders(1) => [{:member=>"member_10", :rank=>1, :score=>10.0}, {:member=>"member_9", :rank=>2,

    :score=>9.0}, {:member=>"member_8", :rank=>3, :score=>8.0}, {:member=>"member_7", :rank=>4, :score=>7.0}, {:member=>"member_6", :rank=>5, :score=>6.0}, {:member=>"member_5", :rank=>6, :score=>5.0}, {:member=>"member_4", :rank=>7, :score=>4.0}, {:member=>"member_3", :rank=>8, :score=>3.0}, {:member=>"member_2", :rank=>9, :score=>2.0}, {:member=>"member_1", :rank=>10, :score=>1.0}] Get page number 1 from the leaderboard
  5. ruby-1.9.2-p180 :014 > highscore_lb.leaders (1, :with_scores => false, :with_rank =>

    false) => [{:member=>"member_10"}, {:member=>"member_9"}, {:member=>"member_8"}, {:member=>"member_7"}, {:member=>"member_6"}, {:member=>"member_5"}, {:member=>"member_4"}, {:member=>"member_3"}, {:member=>"member_2"}, {:member=>"member_1"}] Get page number 1 from the leaderboard (without scores or ranks)
  6. ruby-1.9.2-p180 :015 > 50.upto(95) do |index| ruby-1.9.2-p180 :016 > highscore_lb.rank_member("member_#{index}",

    index) ruby-1.9.2-p180 :017?> end => 50 ruby-1.9.2-p180 :018 > highscore_lb.total_pages => 3 Add more members to the leaderboard
  7. ruby-1.9.2-p180 :019 > highscore_lb.around_me('member_53') => [{:member=>"member_65", :rank=>31, :score=>65.0}, {:member=>"member_64", :rank=>32,

    :score=>64.0}, {:member=>"member_63", :rank=>33, :score=>63.0}, {:member=>"member_62", :rank=>34, :score=>62.0}, {:member=>"member_61", :rank=>35, :score=>61.0}, {:member=>"member_60", :rank=>36, :score=>60.0}, {:member=>"member_59", :rank=>37, :score=>59.0}, {:member=>"member_58", :rank=>38, :score=>58.0}, {:member=>"member_57", :rank=>39, :score=>57.0}, {:member=>"member_56", :rank=>40, :score=>56.0}, {:member=>"member_55", :rank=>41, :score=>55.0}, {:member=>"member_54", :rank=>42, :score=>54.0}, {:member=>"member_53", :rank=>43, :score=>53.0}, {:member=>"member_52", :rank=>44, :score=>52.0}, {:member=>"member_51", :rank=>45, :score=>51.0}, {:member=>"member_50", :rank=>46, :score=>50.0}, {:member=>"member_10", :rank=>47, :score=>10.0}, {:member=>"member_9", :rank=>48, :score=>9.0}, {:member=>"member_8", :rank=>49, :score=>8.0}, {:member=>"member_7", :rank=>50, :score=>7.0}, {:member=>"member_6", :rank=>51, :score=>6.0}, {:member=>"member_5", :rank=>52, :score=>5.0}, {:member=>"member_4", :rank=>53, :score=>4.0}, {:member=>"member_3", :rank=>54, :score=>3.0}, {:member=>"member_2", :rank=>55, :score=>2.0}] Get an “Around Me” leaderboard
  8. Get rank and score information for an arbitrary list (e.g.

    friends) ruby-1.9.2-p180 :020 > highscore_lb.ranked_in_list(['member_1', 'member_62', 'member_67']) => [{:member=>"member_1", :rank=>56, :score=>1.0}, {:member=>"member_62", :rank=>34, :score=>62.0}, {:member=>"member_67", :rank=>29, :score=>67.0}]
  9. Performance Metrics • Rank 10 million players with sequential scores

    • 15 minutes or ~10,600 writes/sec* • Request an arbitrary page from the leaderboard (50,000 x) • 0.001513 seconds on average • Rank 10 million players with random scores • 36 minutes or ~4,500 writes/sec* • Request an arbitrary page from the leaderboard (50,000 x) • 0.001461 seconds on average *Numbers obtained while working so YMMV
  10. Leaderboard Usage • Agora Games middleware platform • GameBattles •

    http://gamebattles.majorleaguegaming.com/ leaderboards • activity_feed gem • https://github.com/agoragames/ activity_feed
  11. Summary • Redis • Data structure server • Sorted sets

    are useful • Leaderboard gem • Wraps low-level Redis commands • Sorted sets are more useful • Performance • Redis is fast • Usage • Real-world and not an “experiment”
  12. Porting Leaderboard • php-leaderboard • https://github.com/agoragames/php-leaderboard • java-leaderboard • https://github.com/agoragames/java-leaderboard

    • scala-leaderboard • https://github.com/agoragames/scala-leaderboard • python-leaderboard • https://github.com/agoragames/python-leaderboard • erlang-leaderboard • https://github.com/agoragames/erlang-leaderboard • node-leaderboard • https://github.com/omork/node-leaderboard client library, data structures, iteration, TDD, packaging