jaybe78
Fetch online users's friends for large quantity of users
Hello,
I’m designing a bidirectional friendship system where I store the data in a single dynamodb “Users” table.
For the record I only store one side of the relationship and use a GSI to get the edges.
Each user could have between 1 and 1000 friends or more, and I could have 30_000 or more users joining the app at the same time.
I want to develop a feature where when any user who joins the app, he can quickly sees his friends that are online.
Obviously that involves querying friends for every single users joining and also probably caching them (:ets ?).
Each user joining is tracked using Phoenix.track() against his channel(“user:*”) so that I know whether someone is online or not. Their status is stored in an ETS table.
My initial idea is to fetch users’s friends and filter them with what is stored in the ETS table mentioned just before, to know whether one user is online or not.
Obviously the complexity here is that I have to fetch a lot of data at the same time, it represents a lot of queries, so I thought about using genstage or broadway…
May be I can also get away querying less…
For example if user 1, 2 and 3 are friends and the first query executed is for user 1,
I would already get his relationship with 2 and 3, that I could cache !?
May be store in an ETS table each relationship for each user !?
1 => 2
1 => 3
2 => 1
2 => 3
…
Then before querying friends, for users 2 and 3, I can already notify them that user 1 is online.
If anyone could provide some guidance to set up something scalable.
cheers
Cheers
Marked As Solved
boxxxie
I think it is worth it to try a simpler solution first. have a subscription for all of a users friends instead of a sub for each online friend. on the server you may want to use 2 lists per user for figuring out dispatch/rendering. a list of their friends, and a list of observers. maybe these 2 lists sound redundant, but you’ll be able to add a backoffice or followers without doing hacky stuff to the render list. when something changes with a user you iterate through the observers and dispatch status. if you get to the point where this kills your server then you can use something like a message queue for update dispatching, or use delays.
Also Liked
jhogberg
Have you considered using a relational database instead? They excel at solving this kind of problem.
jhogberg
It has everything to do with the database used: the fastest operation is the one that never runs, and with a relational/graph database you can avoid the reads altogether. You ask the database for the friends which are online and get only those in the result, deferring the retrieval of the less-interesting offline friends for later, which may very well be cached end-user-side (e.g. localStorage) on a long-term basis.
The moment that you start doing relational operations over data that you have retrieved from a NoSQL database, you have invented your own half-finished, slow, and bug-ridden relational database.
You will need to adapt the application to make good use of the relational model, but once you have, a sustained “50000 users with 1000 friends each leaving and joining every second” – and likely many of your other problems too – become a non-issue.
Not all your data needs to be in a relational database either, but you may as well put it there: in my experience every clever NoSQL project ends up needing relations everywhere sooner or later anyway.
garrison
You have two predicates.
userhas afriendrelationship withcurrent_useruserhasstatus=online
Assuming you have two indexes, for each of these predicates, you have two choices:
- scan user’s friends and then filter for
status=online - scan all
onlineusers and then filter for friendship
If you use a relational database it will pick one for you - almost certainly the first, because the set of friends is going to be a lot smaller than the set of all online users.
Of course, there is a third option: you could create an index of all online friends, essentially materializing the predicates.
But the crux of the issue here is: you seem to be storing the online status in ephemeral :ets tables instead of in the database (not necessarily a bad idea). So you are responsible for performing the filtering yourself. Meaning you are back to the same choices:
- scan all
onlineusers from the ets table and then filter for friends - scan all
friendsfrom the database and then filter for online
The third option (indexing on both fields) is no longer available to you because you have no way to atomically update both the database and your ets table, so you risk corrupting the state. Of course maybe this is not a big deal since your online state is so ephemeral anyway.
I think you had the right idea from the beginning here - this is what you should do IMO. Also consider the following: it is common for a “friends list UI” to show both online and offline friends. I would go so far as to say this is the default. So you’re probably not losing much querying the whole friends list anyway - you may as well just display it!
garrison
Well yeah, you would sort the friends online-first in the UI. This is what literally every friends list UI I’ve ever seen does.
“Scanning” in my reply was a technical term referring to how the database executes your query. You would be writing a query, the database is scanning. You would of course want an index on the thing you’re scanning, which in this case would be the friends relation. You would want an index on (user_id, friend_id) to make that fast. I would just use that tuple as the primary key and kill two birds with one stone.
Obviously you would want that to be bidirectional so I guess you would either create two friends entries (one each way) for every pair of friends or create a second index and scan both. I’m not sure if there is a better way to model that.
Materialized views are not updated incrementally in Postgres IIRC but all at once, so obviously that creates performance problems. I can’t speak to partitioned tables, maybe someone else has experience with that.
What you are describing here is just caching, right? Sounds good to me.
garrison
Careful: if you retrieve and track only the online friends then users who come online after you look up the friends will never appear.







