2

I am using redis to implement the leaderboard. The problem statement I am addressing is - given a user, get five users above him, and five users below him in the leaderboard.

Following is the approach, that I have taken, please let me know, if it is optimal, or something better can be done:

1. lower_key = zrank('set_name', 'member_name') // get the position of the user in the set
2. higer_key = zcard('set_name') // the total no. of elements in the leaderboard
3. low = max(0, lkey-5) // edge-case if user rank is less than 5.
4. high = min(key+5, higher_key) // edge-case if user rank lies is top-5
5. zrange('set_name', low, high) // get the range between the intervals. 

zrank is O(log(N))
zcard is O(1)
zrange step is O(log(N)+M) 

Is there a better way to perform this operation?

EIDT : One of the answer mentioned about too much of back and forth switching, hence I added a pipeline, please have a look at the implementation -

pipeline = self.redis_connection.pipeline()
lkey = pipeline.zrank(leaderboard_name, member)
hkey = pipeline.zcard(leaderboard_name)
inter = int(self.DEFAULT_PAGE_SIZE)/2
low = max(0, key-inter)
high = min(key+inter, hkey)
pipeline.zrange(leaderboard_name, low, high)
return pipeline.execute()

Please let me know your thoughts.

user1629366
  • 1,931
  • 5
  • 20
  • 30
  • Maybe you should just use a noSql db instead of a simple key-value store. – user1077063 May 16 '13 at 14:17
  • Could you please elaborate on how would that make a difference? I have heard redis is great for performance, and is custom-designed for use cases as these. – user1629366 May 16 '13 at 14:30
  • Redis is perfect for implementing real-time leadership boards. You're doing the right thing. See an example at http://blog.agoragames.com/blog/2011/01/01/creating-high-score-tables-leaderboards-using-redis/ among other places. Mentioning to counter-balance @user1077063. – Eli May 16 '13 at 20:51

1 Answers1

1

So, your current approach is fine and works (aside from the typos in the variable names), but requires a lot of back and forth between your client and your redis server, and that's usually where Redis' bottleneck ends up. The back and forth in your case is unnecessary, as you can actually do everything in a single LUA script that you then run as a Redis command from your client. Everything is done on the Redis server then, and there's only one back and forth instead of the 3 in your case.

Here's how I would do it in LUA (untested):

local key_idx = redis.call("ZRANK", KEYS[1], ARGV[1])
local card_idx = redis.call("ZCARD", KEYS[1])
local low_idx = math.max(0, key_idx-5)
local high_idx = math.min(key_idx+5, card_idx)
local return_arr = redis.call("ZRANGE", KEYS[1], low_idx, high_idx)
return return_arr

You'd then call this from redis as:

redis-cli eval "$(cat ./myscript.lua)" 1 sorted_set_name, member_name
Eli
  • 36,793
  • 40
  • 144
  • 207
  • thanks for the answer, how about doing the things using `pipeline` i guess they would reduce the back and forth, right? – user1629366 May 17 '13 at 03:31
  • You can't do them using a pipeline since they depend on each other. Lua is your only option if you want to avoid back and forth. See http://www.terminalstate.net/2011/05/redis-pipelines-and-transactions.html and http://stackoverflow.com/questions/9612743/can-redis-pipeline-multiple-commands-that-depend-on-previous-ones – Eli May 17 '13 at 04:46