Reputation: 843
I have implemented a leader board using sorted sets in redis. I want users with same scores to be ordered in chronological order, i.e., user who came first should be ranked higher. Currently redis supports lexicographical order. Is there a way to override that. Mobile numbers are being used as members in sorted set.
One solution that I thought of is appending timestamp in front of mobile numbers and maintaining a hash to map mobile number and timestamp.
$redis.hset('mobile_time', '1234567890', "#{Time.now.strftime('%y%m%d%H%M%S')}")
pref = $redis.hget('mobile_time, '1234567890'')
$redis.zadd('myleaderboard', "1234567890:#{pref}")
That way I can get rank for a given user at any instance by adding a prefix from hash.
Now this is not exactly what I want. This will return opposite of what I want. User who comes early will be placed below user who comes later(both with same score).
Key for user1 = 201210121953**23**01234567890 score: 400
key for user2 = 201210121253**26**09313123523 score: 400 (3 seconds later)
if I use zrevrangebyscore, user2 will be placed higher than user1.
However, there's a way to get the desired rank:
users_with_higher_score_count = $redis.zcount("mysset", "(400", "+inf")
users_with_same_score = $redis.zrangebyscore("mysset", "400", "400")
Now I have the list users_with_same_score with correct ordering. Looking at index I can calculate rank of the user.
To get leader board. I can get members in intervals of 50 and order them through ruby code. But it doesn't seems to be a good way.
I want to know if there's a better approach to do it. Or any improvements that can be made in solution I purposed.
Thanks in advance for your help.
P.S. Scores are in multiples of 50
Upvotes: 3
Views: 1470
Reputation: 1170
If you are displaying leaderboard in descending order of score then I don't think the above solution will work. Instead of just appending timestamp
in the score you should append Long.MAX_VALUE - System.nanoTime()
So your final score code should be like -
highscore = 100
timestamp = Long.MAX_VALUE - System.nanoTime();
redis.zadd('myleaderboard', highscore + '.' + timestamp, playerId);
Now you will get the correct order when you call redis.zrevrange('myleaderboard', startIndex, endIndex)
Upvotes: 0
Reputation:
The score in a sorted set supports double precision floating point numbers, so possibly a better solution would be to store the redis score as highscore.timestamp
e.g. (pseudocode)
highscore = 100
timestamp = now()
redis.zadd('myleaderboard', highscore + '.' + timestamp, playerId)
This would mean that multiple players who achieved the same high score will also be sorted based on the time they achieved that high score as per the following
For player 1...
redis.zadd('myleaderboard', '100.1362345366', "Charles")
For player 2...
redis.zadd('myleaderboard', '100.1362345399', "Babbage")
See this question for more detail: Unique scoring for redis leaderboard
Upvotes: 3
Reputation: 3197
The external weights feature of the sort command is your saviour here
SORT mylist BY weight_*
Upvotes: 0