This content originally appeared on Level Up Coding - Medium and was authored by Gaurav Agrawal
Leaderboards are everywhere, and audiences love seeing their ranks up top. But you know what they love even more, Realtime Leaderboards!
And yes, leaderboards are a hard problem to solve, especially making them realtime. So in this post, let’s see how we Redis can be used to implement blazing fast realtime leaderboards.
Why Redis for RealTime Leaderboards?
Leaderboards are fundamentally a sorting problem, and Redis provides battle-tested Sorted Sets data structure out of the box, which can be used to build realtime leaderboards.
Sorted Sets Sure, What else?
Redis’s is built on single-threaded architecture, and this allows score upsert requests to be sequentially processed. Which means the leaderboards will remain consistent.
Baked in Performance: Sorted sets gets you O(log(n)) for most operations, where n is the number of members in your set.
Redis’s persistence options allows you to keep backup of your data, so in case of any downtime, you don’t loose it all.
Solution Overview:
While a realtime sorted set might answer your score sorting problem, but you might find yourself facing some common problems like these:
- What if I want to have Username, and Avatars in my leaderboard?
- What if my ranking logic is based on multiple factors, like a SQL multi-column order by?
Because you only get to keep score and member identifier in a sorted set row, you can resolve the Username, Avatars etc data from somewhere else. Ideally it should be easy if you use user ids as the member string, which should allow you to resolve anything user related from your system.
For multi column SQL like sorting, you would need to add and subtract weights from your scores to achieve this behaviour.
Example: Level (Highest Priority), Score (Second Highest)
Score -> Level * 10e5 + Score (Assuming Score is limited to 3 digits, and level is to 100)
More about this in detail here.
So a general flow may look like:
- For every submission, derive score and add it to sorted set against user id as member string.
- For every leaderboard fetch request, get the top N member ids from leaderboard, and resolve rest of the details from your user profile data store against the top N user ids.
- Make sure to have a persistence policy set in place at your Redis deployments.
Important Commands:
1: ZADD: Adds all the specified members with the specified scores to the sorted set
2: ZCARD: Returns the sorted set cardinality (number of elements) of the sorted set stored at key.
3: ZREVRANGE: Returns the specified range of elements in the sorted set stored at key. The elements are considered to be ordered from the highest to the lowest score. Descending lexicographical order is used for elements with equal score.
4: ZREVRANK: Returns the rank of member in the sorted set stored at key, with the scores ordered from high to low. The rank (or index) is 0-based, which means that the member with the highest score has rank 0.
Node JS Code:
//Package used : "ioredis": "^5.2.4"
export const upsertScore = async ({score, username, timetaken}, {redis} ) => {
//Giving Highest Priority to Score, inflating it's value to have less impact of time taken.
//Here we are prioritising score desc and tike taken asc.
const modifiedScore = score * 100 - Math.floor(timetaken/10);
return redis.zadd(KEY, modifiedScore, username);
}
export const getRankUser = async ({ key, username }, { redis }) => {
const rank = await redis.zrevrank(key, username);
if(rank >= 0) return rank + 1;
};
export const getRanks = async ({ key, startIndex, endIndex }, { redis }) => {
return redis.zrevrange([key, startIndex, endIndex, "WITHSCORES"]);
};
export const totalCount = async ({ key }, { redis }) => {
return redis.zcard(key);
};
export const getLeaderBoard = async ({ page, perPage, username }, { redis }) => {
const startIndex = perPage * (page - 1);
const endIndex = (perPage * page) - 1;
const [ranks, count, userRank] = await Promise.all([
getRanks({ key: KEY, startIndex, endIndex }, { redis }),
totalCount({ key: KEY }, { redis }),
getRankUser({ key: KEY, username }, { redis })
]);
return {
ranks: ranks,
count: count,
userRank: userRank
}
}
Resources:
Getting Fast Realtime Leaderboards With Redis was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Gaurav Agrawal
Gaurav Agrawal | Sciencx (2022-11-29T12:39:56+00:00) Getting Fast Realtime Leaderboards With Redis. Retrieved from https://www.scien.cx/2022/11/29/getting-fast-realtime-leaderboards-with-redis/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.