After searching for best practices for the classic âFollowersâ/âFollowingâ problem using Parse I ended up with more questions than answers. So, I am hoping that by listing here all the potential solutions we can come up with a definitive summary of the upsides and downsides of every approach and figure out which would be the best way to implement this very common scenario.
I have to say that I am not a database expert, so some of my assumptions might be stupid, but that would hopefully help in clearing things up by people who understand much much more than I do.
So, from what I found so far there are a few approaches that can be used to implement a system like this, basically an Instagram clone with potential hundred of thousands or even millions of users, followers and followees.
1. Put everything in the _User table
This is probably the worst option and I donât expect many explanations as to why, I think most people agree to this. Nevertheless, letâs explore the ways this could be achieved:
a) Use two Array columns
Table _User {
/* Everything that comes out of the box */
followers: Array<String(objectId)>;
following: Array<String(objectId)>;
}
b) Use two Relation columns
Table _User {
/* Everything that comes out of the box */
followers: Relation<_User>;
following: Relation<_user>;
}
2. Create a âOne user one lineâ type of relation table
I know that âOne user one lineâ sounds like a very noob-ish way to put it, but I believe itâs an easier way for people to understand. Anyway, this would looks like this:
Table Following {
user: USER_TYPE;
followers: FOLLOW_TYPE;
following: FOLLOW_TYPE;
}
Now, here we have a few options for the USER_TYPE and FOLLOW_TYPE:
a) USER_TYPE: String(objectId) and FOLLOW_TYPE: Array<String(objectId)>
b) USER_TYPE: String(objectId) and FOLLOW_TYPE: Relation<_User>
c) USER_TYPE: Pointer<_User> and FOLLOW_TYPE: Array<String(objectId)>
d) USER_TYPE: Pointer<_User> and FOLLOW_TYPE: Relation<_User>
3. Create a âMultilineâ type of relation table
Table Following {
user: USER_TYPE;
follower: FOLLOW_TYPE;
following: FOLLOW_TYPE;
}
a) USER_TYPE: String(objectId) and FOLLOW_TYPE: String(objectId)
b) USER_TYPE: String(objectId) and FOLLOW_TYPE: Pointer<_User>
c) USER_TYPE: Pointer<_User> and FOLLOW_TYPE: String(objectId)
d) USER_TYPE: Pointer<_User> and FOLLOW_TYPE: Pointer<_User>
Conclusion
I think there is a clear difference between option 1. and the other two.
I also think it is clear how the coding would differ if we went with either options 2. or 3.
But, it is not clear for many people what are the downsides and upsides when choosing between options 2. and 3. when it comes to performance.
Also, going a bit deeper, which is the better/most performant approach of the a), b), c) and d) âsub-optionsâ when going for options 2. and 3.? Topics like this one(a fairly recent one) Querying a âFollowingâ Table seem to introduce even more confusion as it implies that using Pointer <> type, even if at first glance it seems to be the preferred type in Parse, is less performant than simply querying against strings of objectIds.