Firebase Leader Rating

I have a project that should display the leaderboard of the top 20, and if the user is not in the leaderboard, they will appear in 21st place with their current rating.

Is there an effective way to do this?

I use Cloud Firestore as a database. I believe that it was a mistake to choose it instead of MongoDB, but I am at the center of the project, so I have to do it with Cloud Firestore.

The application will be used by 30K users. Is there a way to do this without getting all 30k users?

this.authProvider.afs.collection('profiles', ref => ref.where('status', '==', 1) .where('point', '>', 0) .orderBy('point', 'desc').limit(20)) 

This is the code I made to get the top 20, but what would be the best practice to get the current registered user rating if they are not in the top 20?

+18
source share
3 answers

Searching for a player’s arbitrary rank in the leaderboard, so that scale is a common complex database problem.

There are several factors that can help you solve a problem, for example:

  • Total number of players
  • Rate individual players adding points
  • Rate new points added (concurrent players * higher)
  • Evaluation Range: Limited or Unlimited
  • Distribution of points (uniform or their "hot points")

Simplified approach

A typical simplified approach is to count all players with a higher score, for example SELECT count(id) FROM players WHERE score > {playerScore} .

This method works on a small scale, but as your player base grows, it quickly becomes slow and resource intensive (both in MongoDB and Cloud Firestore).

Cloud Firestore does not support count , as it is a non-scalable operation. You will need to implement it on the client side, simply by counting the returned documents. In addition, you can use the Cloud features for Firebase to perform server-side aggregation to avoid the additional bandwidth of returned documents.

Periodic Update

Instead of giving them live ranking, change it to update only as often as every hour. For example, if you look at the overflow stack rating, they are updated only daily.

For this approach, you can schedule a function or schedule app Engine if it takes more than 540 seconds to run. The function will write out a list of players, as in the ladder collection, with a new rank field filled in with the rank of players. When a player scans the ladder now, you can easily get the top X + your own player rating in O (X).

Even better, you could further optimize and explicitly write out the top X as a separate document, so to extract the ladder you only need to read 2 documents, the top X and the player, saving money and speeding them up.

This approach will really work for any number of players and any recording speed, as this is done out of range. You may need to adjust the frequency, but as you grow, depending on your willingness to pay. 30 thousand Players every hour will be $ 0.072 per hour ($ 1.73 per day) if you have not done the optimization (for example, ignore all 0 points, since you know that they are connected last).

Inverted index

In this method, we will create a slightly inverted index. This method works if there is a limited range of points, which is significantly less than the number of players (for example, 0-999 points versus 30 thousand players). It can also work in an unlimited range of points, where the number of unique points is still significantly less than the number of players.

Using a separate fee called “points”, you have a document for each individual account (nonexistent if it is not), with the player_count field.

When a player receives a new total score, you will make 1-2 entries in the scores collection. One entry - from +1 to player_count for their new score, and if this is not the first time, -1 to their old score. This approach works both for “your last score is your current score” and for your “best scores”.

Finding the exact rank of a player is as simple as something like SELECT sum(player_count)+1 FROM scores WHERE score > {playerScore} .

Since Cloud Firestore does not support sum() , you do higher, but summarize on the client side. +1 is because the amount is the number of players above you, so adding 1 gives you a player rating.

Using this approach, you will need to read a maximum of 999 documents, an average of 500ish, to get a player rating, although in practice this will be less if you delete ratings that have zero players.

The speed of recording new points is important for understanding, since you can only update an individual score every 2 seconds * on average, which for a well-distributed range of points from 0 to 999 means 500 new points / second **. You can increase this by using distributed counters for each point.

* Only 1 new score in 2 seconds, as each point generates 2 entries
** Assuming an average game time of 2 minutes, 500 new points per second can support 60,000 simultaneous players without distributed counters. If you use the "Highest Score - Your Current Score", it will be much higher in practice.

N-wood shard

This is by far the most difficult approach, but it can allow you to have both faster and fixed positions in real time for all players. It can be considered as a read-optimized version of the Inverted Index approach above, while the above inverted index approach is a write-optimized version.

You can follow this related article for “Fast and Reliable Ranking in the Data Warehouse” for the overall approach. For this approach, you will want to have a limited score (this is possible with unlimited, but will require changes from below).

I would not recommend this approach, since you will need to do distributed counters for top-level nodes for any ladder with half-frequency updates, which will probably negatively affect the benefits of reading.

Trinity Tree Example

Final thoughts

Depending on how often you show the leaderboard for players, you can combine approaches to optimize this a lot more.

Combining the Inverted Index with Periodic Update over a shorter time interval can give you access to O (1) for all players.

As long as the leaderboard is viewed over all players> 4 times during the Periodic Update period, you will save money and get a faster leaderboard.

Essentially every period, say, 5-15 minutes, you read all the documents from scores in descending order. Using this, keep the total number of players_count . Rewrite each point in a new collection called scores_ranking with a new players_above field. This new field contains the current total, excluding current player_count .

To get a player’s rank, all you have to do now is read the player’s score with score_ranking → Their rank is players_above + 1.

+46
source

One solution, not mentioned here, that I’m going to implement in my online game and that can be used in your case, is to rank the user if he is not in the visible leaderboard, because, frankly, the user does not recognize (or anyway?), do they occupy the 22,882th place or the 22,838th place.

If the 20th place scored 250 points, and the total number of players is 32,000, then each point below 250 costs an average of 127 places, although you can use a certain curve so that they move up the point to the bottom of the leaderboard they are not visible jump exactly 127 places each time - most jumps in the ranking should be closer to zero.

It is up to you to decide whether you want to define this rating as a rating or not, and you can add random salt to the number so that it looks reliable:

 // Real rank: 22,838 // Display to user: player rank: ~22.8k // rounded player rank: 22,882nd // rounded with random salt of 44 

I will do the last.

+2
source

A solution not mentioned by Dan is to use security rules in conjunction with Google's cloud-based features.

Create a high score map. Example:

  • highScores (top20)

Then:

  • Give users write / read access to highScores.
  • Give the document / card highScores the smallest result in the property.
  • Allow users to write only in highScores if its rating is> the lowest rating.
  • Create a recording trigger in Google cloud features that are activated when recording new highScore. In this function, remove the lowest score.

This looks like the easiest option. It is also in real time.

0
source

Source: https://habr.com/ru/post/1272577/


All Articles