How to build a ranking system


#1

Let’s say I have a Mongo collection with data that is structured like this:

[
  {
    "username": "blah",
    "score": 55
  },
  {
    "username": "john",
    "score": 64
  },
  {
    "username": "emily",
    "score": 40
  },
]

And say there are 100k records of users in the database.
How would I make a performant ranking system so that I can search for a username and the app retrieves the rank the user has.

For example:
Search for username “emily”, returns rank #3 (she has the lowest score of the three users).

So far I’ve tried to do this:

var emilyScore = Users.findOne({"username": "emily"}).score;
var rank = Users.find({rank: {$gt: emilyScore}}).count() + 1;

This works, but is not performant. I have not come up with a better solution yet. Any suggestions are welcome!


#2

Quick question, have you created an index on score?

Anyhow, for a large dataset and rankings, if you don’t want to bring in some other technology like redis into the mix, I’d say go for non-realtime solutions such as:

  1. calculate ranks every N minutes and write them on the user doc itself (such large bulk updates tend to flood the oplog, though)
  2. do (1), but mark the first nth (1000 perhaps) users with exact rank and mark others like 1000+ or (1000+, 10000+ etc) since no one would actually care if they are the 23493rd :slight_smile: (perhaps)
  3. do (2) but keep the ranks in a separate collection
  4. do (2) or (3) but after the first 1000, store a normalized score which is based on average score, min and max scores and the normalized score being max 100k (number of users) which would give you a rough estimate of the user’s position, which you can round up. eg a normalized score of 75423 would be presented as 75400’s.
  5. do (4) but store the normalized score as a percentile and tell the user that they are in the top 47 percentile.

I’m sure this is kind of brain melting, but if we are talking performance and the data set keeps growing, and you don’t want to add in new tech, these options would become worth exploring.


#3

for THAT many users, I’d have two collections, one as you stated above and another:

score:
{
    1: { 'joebloe': 1, 'bobthebuilder':1},
    2: ....
    55: { 'blah':1},
    100 : { } // here's to hoping you only have 100 points as max
}

When you find a user, u can find their score, then you go to the score table and count the users before them, that’s their rank. I think mongo does this for free? as in keep a count of array numbers, if it doesn’t you do it.

And when you inc/dec a score, it’s as simple as removing the key from the score collection and inserting it into the new score and updating your user’s score field.

Why object instead of array? Cause you may have [ 50k users on 0 points ], don’t know how mongo would fair $push/$pulling that…