Justin Frazer
Justin Frazer

Reputation: 970

Swift/Firestore Query a Collection in a Ranking System

I've grown to appreciate how much I've learned form the SO community, so thank you in advance for your time.

Here's a summary of what I need to accomplish:

Say I have 1,000 users in a Firestore Collection and each user has an associated score. Users are also able to follow one another. Let's say 'User A' needs to run a query on the 100 users that he/she is following while maintaining the rank of each user within the global 1,000 users. I.E. User A is following the rank #1 person, the rank #237 person, the rank #999 person, etc.

My data is currently structured as seen below:

user_information (Collection)
-- user_id (Document)
---- user_score (Int Field) <- the field in which the query is sorted by
users (Collectiion)
-- user_id (Document)
---- following_users (Collection) <- Running the query on these users
------ user_id (Document)

It would be easy enough to sort the following_users by their scores relative to one another, but how do I maintain the rank within the global leaderboards while only querying on the 100 users that are being followed? Is there a different approach that I need to take to accomplish such a task?

Upvotes: 0

Views: 178

Answers (1)

MarketerInCoderClothes
MarketerInCoderClothes

Reputation: 1226

Before I answer your last question, let me share this: My approach would be to tweak your db structure in the following way:

user_information (Collection)
-- user_id (Document)
---- user_score (Int Field) <- the field in which the query is sorted by
users (Collectiion)
-- user_id (Document)
---- following_users (Array Field) [Array field of user_id's this user is following]

By making the following_users an Array instead of a collection, you won't have to maintain the same user_id document in potentially hundreds of places.

Let's say users A, R, D, S, T, (...) all follow the user H, this would mean that your user H's document should be the same everywhere. You would have to use transactions to make sure the user rank is the same everywhere, whenever it changes. And this is potentially the same for any new fields you add to your user_id documents.

Scale this to just 10'000 users, and you're making probably 100X the number of write operations than what you really need.

By only referencing the followed users in an Array, the process would be like this:

  1. Query the current user document
  2. (optional): Supply a UI button to "Show users I follow"
  3. Request the documents that are in that array, and sort by rank maybe.

If you'd like to skip step #2, then you can directly query the followed users, ordered by rank, and limited to 10 results for example. And then scroll to load more or click next. (search for 'paginated firestore queries' or 'firestore query cursors').

Now, to answer your question "but how do I maintain the rank within the global leaderboards while only querying on the 100 users that are being followed?"...

In this case, I would do this:

Introduce a new field in your user_id document, called: 'followerCount', make it an Integer.

  1. Every time a user decides to follow another user, I would add the followed to the follower's "following_users" Array field.
  2. At the same time, I would increment the 'followerCount' of the followed user.
  3. If the follower decides to unfollow, I would delete the followed user_id from the "following_users" array and decrement the "followerCount" of the followed user.

Side note, I would do such operations using "Firestore Transactions" (google it) to make sure they happen at the same time.

Side note #2: in Firestore, you can increment by -1 to decrement.

Side note #3: on the following_users array field, I would encourage you to use FieldValue.arrayUnion to add user_id's and FieldValue.arrayRemove to remove them.

Having structured things this way, it would be very easy to perform a query on your leaderboard. Example:

await leaderboard_query = db.collection(user_information).where('followerCount', '>', 0).orderBy('user_score');

Upvotes: 1

Related Questions