jaybe78

jaybe78

Implementing distributed user counters

Hello,

I’ve recently developed a module that creates distributed user counters, based on what’s described in this post

Basically, locally I use ETS table to atomically know how many people there is in a channel, then I aggregate all counters of all nodes using Phoenix Tracker.

In summary, users starts by joining “room:lobby”, using ETS table, I check what’s the last room id with available slots( <= 70), and if I can’t find one, I assign them to a new topic.

Those topics (“room#{id}”) are created in ascending order (“room#1”, “room#2”, …), and I use the same topics on every nodes.

With that in place I’m able to create channel topics with a max size of 70 or so users.

It works quite well overall, but I have a “problem” which concern the total number of users on channel topics.

As I said previously, the first step before tracking counters across nodes, is to use ETS table locally, to know the current count on topic, so obviously the value I get only concern users who join on that node.

Therefore if I deploy my app on 10 nodes, since the max is 70, I will get 700 users join on every single topic.

It’s not good for me because I don’t want those rooms to be too crowded. (I plan on using Phoenix Presences on those channels, which does not scale very well when there’s too many users)

1) First solution:
To solve that, my first thought was to divide the max, by the number of actives nodes:
For example on 10 nodes, max => 7 users

That means locally 7 users max can join a channel topic.

This works but it’s not perfect either because the users will be load balanced to different nodes.
They might fill room topics on certain nodes faster than on others, so it would create new topics with barely any users while the previous rooms are not filled completely yet.

2) Second solution

To avoid that problem, I could broadcast to every nodes when a specific room is full or since room id are created in ascending order, I could regularly inform the last id with available slots

This would occur in the room tracker after the aggregate.

The only issue is that, there’s always a delay between the moment where users are populated to a specific channel and the aggregate. That information would always arrive too late…

At this point, I\m running out of ideas :frowning:

Most Liked

garrison

garrison

I’m a little confused with what’s going on in this thread. There’s an implication here that Redis is somehow different than using ETS tables, but there is no reason you can’t just put an ETS table on one node and use it exactly like you would use Redis! You would have no fault tolerance and higher latency, just like Redis. The only reason you would want to actually use Redis is if you wanted its vast array of fancy data structures, which in this case maybe you don’t, seeing as you have already implemented your system on ETS.

Again, it’s perfectly valid to use Redis for its functionality, but “ETS and GenServers” were absolutely designed to be used “cross-node”. OTP literally could not be more designed for that! It’s, like, the entire thing!

This is not a literal answer, but the quintessential example would be something like an exchange/order-matching system: something which processes (financial) transactions. If you want to learn more about that the magic keywords are “LMAX architecture”, plus check out Tigerbeetle for a modern fault-tolerant rendition.

Apologies in advance for nitpicking something you’re probably fully aware of, but CAP and “centralized/distributed” are separate concepts. You can have a distributed consistent system, and you can also have a single-threaded inconsistent system, though this is less common because in a single-threaded system consistency is so easy to achieve.

LostKobrakai

LostKobrakai

The big change here is not GenServer vs. Redis though. It’s centralized storage of data (CP) vs. distributed storage of data (eventual consistency / AP)

LostKobrakai

LostKobrakai

Your remarks make it sound like there’s no real reason for the number 70 exactly, so I’d suggest not going with a fixed cutoff point, but rather evalute a solution using low and high watermarks. Start creating new channels at a low watermark, but also make it so only the high watermark makes that number of users be a problem in a channel. That way if people are added to a channel after the low watermark is reached isn’t directly a problem. You can tune those numbers against expected latency of coordination between nodes as well as expected arrival rates of users. This makes even more sense if you seem to be moving people around anyways.

The part about people potentially landing in empty channels cannot really be avoided though. There will always be the case of all existing channels being considered full and the new person being put into a new channel. It’s however a matter of how likely you make that case. E.g. if your balancer does its job how likely is it for that case to happen.

I have no idea about scaling persence, but I’m asking because both likely affect performance in distinct ways. There’s no single knob to dialing performance. I’d also argue that performance work is best evaluated by benchmarking your solution besides reading what others have to say.

Phxie

Phxie

If you want to use ETS that’s fine, I was just pointing out that Redis is designed to handle what you’re trying to do in a way that ETS and Genservers are not. Your trying to add cross node functionality to tools that are for local use, rather than use a tool that will work cross node by default.

If you were using a sorted set you wouldn’t be making this post because it solves the exact issue your post is about with regards to counting and distributing users to rooms atomically.

Again, you mentioned this above as well. With a sorted set you would just go down the list until you find a vacant spot and then fill it. If no vacant spots exist you create a new room. Sorted sets are ordered by highest counts.

I should also add, one of the reasons I’m pushing Redis so hard is that I was desgning something with ETS/Genservers a year or so ago for a couple of week before I hit a wall and finally tried Redis. Redis made everything 10x easier for me.

Phxie

Phxie

Yes.

Redis can give you an atomic, cross‑node count while users join and leave in real time. It won’t match ETS’s raw speed, but building your own synchronization and network‑transfer logic for ETS updates erodes that advantage. At high join/leave volumes, custom distribution code risks race conditions and latency that Redis’s built‑in counters avoid.

You will never get 100%, always correct counts when dealing with hundreds of thousands of users connecting at the same time, but at least with Redis the results will be atomically consistent.

No.

ETS is local, it will always be faster than using an external source.

However, I should also add that although ETS is faster locally you still presumably have to manually process users/counts and send info between nodes. Your custom data distribution logic will not magically be done in an instant, especially at the scale of hundreds of thousands of users constantly joining and leaving.

I should add, your posts “problem” is the below. You have only mentioned speed whilst trying to counter Redis, but I recommended Redis because it answers your posts problem. You’ve changed the argument from “I can’t get the counts correct” to “but it’s gotta be fast”.

I’m not going to reply any further, as I feel I’ve expressed my opinion multiple times at this point and it would be a waste of time for me to continue to push a tool you obviously have no interest in using.

Apologies if I’ve annoyed you by turning this into ETS vs Redis, but I still maintain Redis is the correct tool for the job for track user presence with atomic room counts.

Where Next?

Popular in Questions Top

pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
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
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
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
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
sabri
Can someone explain the settings of pool_size of Ecto in config file? and what is the recommend size? Thanks
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
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
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New

We're in Beta

About us Mission Statement