10. O F D S S’ score player t stamp
1 2 1 1 3 150 adam 2
2 3 1 1 3 150 mike 5
3 1 1 1 3 150 ronald 1
4 4 2 4 4 121 sam 7
5 6 3 5 7 100 adam 2
6 5 3 5 7 100 mark 1
7 7 3 5 7 100 robert 3
how is rank calculated in SQL ?
11. (D)
SELECT
id, player, score,
@prev := @curr, @curr := score,
@rank := IF(@prev = @curr, @rank, @rank+1) AS rank
FROM
lb,
(SELECT @curr := NULL, @prev := NULL, @rank := 0) sel1
ORDER BY score DESC;
(S)
SELECT a.player, a.points, COUNT(b.id) rank
FROM simple_leaderboard a, simple_leaderboard b
WHERE a.points < b.points OR (a.points = b.points AND a.player =
b.player)
GROUP BY a.player, a.points
ORDER BY a.points DESC, a.player ASC;
(S’)
SELECT a.player, a.score, COUNT(b.id) rank
FROM lb a, lb b
WHERE a.score <= b.score OR (a.score = b.score AND a.player = b.player)
GROUP BY a.player, a.score
ORDER BY a.score DESC, a.player ASC
13. REALTIME RANKING
• even with pagination, need for full table scans
• even with good indexes, high complexity
• large sets increased tied items or larger scores
17. REDIS MEETS LEADERBOARDS
Sorted Set
key score member
O(1) ~ O(log(N)+M)
• ZADD key score member
• ZCARD key
• Z(REV)RANK key member
• Z(REV)RANGE key member start stop [ws]
• Z(REV)RANGEBYSCORE key member min max [ws] [limit offset]
23. redis_leaderboard.rb 1
after_save :dump_to_redis
after_destroy :remove_from_redis
dump to redis and remove from redis methods know™ which columns
need to be ranked and will perform the ZADD redis command that will
insert or update the score in the SortedSet it belongs.
ranked_columns.each do |col|
# zadd(key, score, member)
sorted_set.zadd(col.name, self.send(col.name), self.id)
end
25. redis_leaderboard.rb 3
:find_leaderboard ~ cont
once the array of ids is returned by Redis, we just need to fetch those
rows from the database with no effort and `transfer` the rank from the
array to the unsorted collection:
result = all(:conditions => {:id => ids})
ids.each_with_index do |sorted_id, index|
row = result.detect {|lb_row| lb_row.id == sorted_id.to_i}
# attr_accessor :rank
row.rank = index + row_start + 1
end
result = result.sort {|x,y| x.rank <=> y.rank}
# assign result to a WillPaginate collection ...
26. redis_leaderboard.rb 4
:find_around_me
find around me is a classic requirement of ranking tables. It’s really easy to
accomplish this task using just the player id and the cardinality of the
items below and above. first find the rank of the player and use it to
create limit and offset for Z(REV)RANKBYSCORE
num_above_below = 5
player_rank = sorted_set.z(rev)rank(params[:sort_column], player_id)
limit = 2*num_above_below + 1
offset = player_rank - num_above_below
ids = params[:sort_order] == 'desc' ?
sorted_set.zrevrangebyscore(params[:sort_column], limit, offset) :
sorted_set.zrangebyscore(params[:sort_column], limit, offset)
27. redis_leaderboard.rb 5
:find_around_me ~ SQL
find_by_sql(["SELECT lb.* FROM
(SELECT @numrow := @numrow+1 as rn, ldb.*
FROM (SELECT * FROM #{table_name}
WHERE game_type = ?
AND platform = ?
ORDER BY #{sort_column} #{sort_order}) ldb,
(SELECT @numrow := 0) rown) lb,
(SELECT rn FROM
(SELECT @numrow := @numrow+1 AS rn, player_rn.player_id
FROM
(SELECT player_id
FROM #{table_name}
WHERE game_type = ?
AND platform = ?
ORDER BY #{sort_column} #{sort_order}) player_rn,
(SELECT @numrow := 0) rown ) my_pos
WHERE player_id = ?) p_rownum
WHERE lb.rn >= p_rownum.rn-? AND lb.rn <= p_rownum.rn+?
ORDER BY #{sort_column} #{sort_order}",
game_type, platform, game_type, platform, player_id, num_above_below,
num_above_below])