goyalankit
goyalankit

Reputation: 843

Redis zrevrangebyscore, sorting other than lexicographical order

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

Answers (3)

ankit.vishen
ankit.vishen

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

user1000952
user1000952

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

Lloyd Moore
Lloyd Moore

Reputation: 3197

The external weights feature of the sort command is your saviour here


SORT mylist BY weight_*

http://redis.io/commands/sort

Upvotes: 0

Related Questions