jaybe78

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

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

jhogberg

Erlang Core Team

Have you considered using a relational database instead? They excel at solving this kind of problem.

jhogberg

jhogberg

Erlang Core Team

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

garrison

You have two predicates.

  • user has a friend relationship with current_user
  • user has status=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 online users 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 online users from the ets table and then filter for friends
  • scan all friends from 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

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

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.

Where Next?

Popular in Questions Top

stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
New
Kagamiiiii
Student & New to elixir. Nice language. I want to convert a english character, e.g. “a”, which is stored in a variable, to it’s asci...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
clayschick
I'm trying to create a simple query to select distinct values from a column. If I only use select and distinct I get back a list of uniqu...
New

Other popular topics Top

stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
977 41022 311
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New

We're in Beta

About us Mission Statement