How to keep track of potentially 1000s of events participant cap's change?

Hey there,

I have the following problem:
I have events in my database, a lot of them, and the list will only grow.
Every event can have multiple ticket types, and each ticket type has a quantity field.
The sum of the quantity fields for given event’s ticket types is the participant cap.

I want to introduce a waiting list option so if a spot frees up for any reason, being someone leaving or the total participant cap growing I want to send an email to the users on the waiting list.

How do I keep track of the change of the participant cap?

I could create just a function that accepts 2 instances of an event and compare the participant cap, so I can pass it one event being before any action and one after and if the part cap is changed then send the emails.
cons for this is that it is very manual, need to find the places where it is relevant in the code, pro is that it is pretty simple.

Other option could be to have a genserver for each event, keeping the current participant cap for each event, and tracking if messages have already been sent or not. Maybe using:
https://elixirschool.com/blog/til-genserver-handle-continue/
But isn’t this approach very wasteful?
Also somehow would need to make sure that every events has a genserver running, even new ones that get inserted.

Any thoughts on ether of the approaches? Maybe other ideas?

Thanks!

It sound like you could take advantage of both approaches.

If all you need to achieve the task is procedural code maybe you don’t need a GenServer:

def compare(event_1, event_2), do: send_email("email")

But you would need to have received both events to execute this function. If events come and go at different times you could use laziness to defer computation:

def lazy_compare(event), do: 
  (fn later_event -> compare(event, later_event) end)

And just wait until your next input is ready so you can execute the returned function at a later time.

If you really don’t need the GenServer, yes it is a wasteful (but cheap) approach. A GenServer would be a solution to handle asynchronous calls; just be careful because you may already have this in the form of a Phoenix Server, HTTP Request or iex session, but if you’re creating your custom “subscription / publish” mechanism a GenServer would be a great way to go.

If you haven’t, I suggest you read the to spawn or not to spawn article by @sasajuric; it may help to re-arrange some thoughts on when and where to use processes.

2 Likes

Hey, thanks for the response, I will read the article!

Well I do have the two events ready at all times, so that is not a problem, the question is more like, lets say the participant cap can change for x reasons, so let’s say I update a ticket type, then I need to have this function trigger there, or to have a separate entity take care of handling the change. but to me having potentially so many genservers running in an essentially empty state, constantly checking, and waiting for a change is meh…

Sounds a lot like CQRS, and to be honest that kind of pattern really falls outside my area of expertise :grin:.

But if you would like a separate entity to take care of the handling would Task.Supervisor or DynamicSupervisor work for you? That way you would could spawn a process “on request”.

1 Like

Well I could spawn a process for given event every time an action that could change the participant cap could occur, but then I need to make sure to keep it running until long enough to finish it’s task or stop it once I know that there is no change. Otherwise it would just keep running until it is stopped in some way.
So to this the simple function approach is easier to use than this would be.
Basically if I need to start the process on “request”, I would call the function that you sent also, on the same “request”

Definetly.

Ok, so I think I’m starting to have a clearer picture of the approach you want. When an event “goes live” you would like to track its capacity in real-time.

Thinking in “logical terms” here I think if its just 1 or multiple GenServers makes no difference. The purpose of this entity is to track 1 or multiple event caps.

Having multiple GenServers would give you reliability per event in case of failure (assuming you’re not storing on disk or a database, a process crash would lose the tracked event cap). And it may be also an optimization (or not) because you’re taking better advantage of available resources.

1 Like

I have the participant cap in the database per event, just since one event can have many different ticket types I need to add them up, so I would constantly need to do the same query to make sure it is up to date.
Lets say one event has
normal ticket quantity 20
vip quantity 10

so the participant cap for this event now is 30 and I know it from the db.
but if I update vip to 10 or someone who joined left there are new free spots, in order to have the latest I would need to query it constantly per event

I want to track the capacity in real time from the db basically if i go with that solution that keep running all the time

I just noticed a similar threath at Should I use GenServer for this? - #5 by lucaong :grin:

Yeah, so with a GenServer you could “poll the database” each x seconds to keep track of current state.

You could keep a “cache” whenever the cap changes (so you would change it in the database, but would also keep it in memory for real-time purposes). But this sounds clumsy if you have the database at hand.

1 Like

yeah, but if i have 1000s of events, 1000s of genservers polling for their own participant cap every second, isnt that just not worth it really?
or is that very much fine?

I was looking at the EventStore project recently and found about PostgreSQL notifications on the documentation. Could be helpful for what you want to achieve (real-time database tracking).

(edit: conflated links)

1 Like

If I were to go this route 1 genserver per entity would work to keep process spawning/termination “simple”. And 1000 GenServers may be totally fine for your machine (my netbook seems to die at 45,000 GenServers).

But I think the same could be achieved with 100 GenServers handling 100,000 events for example, the role of the GenServer here is more of an optimization so finding where the bottleneck (if any) is key.

In short, with the GenServer approach I think:

  • GenServer helps having the “polling mechanism” reliant.
  • Data reliance/integrity is handled on your database.
  • “How many” GenServers is a performance concern.
1 Like

Thanks, I appreciate your input and time, I will update the post once I have an implementation working!

1 Like

Hope my feedback was helpful :duck: and not to obvious :sweat_smile:

1 Like

It was very helpful already :grin:

btw, if you end up going the Postgres notifications route, I found this article (and the other article the author links to) useful in getting an initial handle on how notifications work: https://medium.com/@kaisersly/postgrex-notifications-759574f5796e

2 Likes

Hey, yes that is the approach I am looking at currently, thanks for the article!

Okay, I ended up implementing the Postgres notifications approach mostly based on this article:

It seems surprisingly good so far!

Thanks everyone for the messages!

3 Likes

That is really cool! :grin: thanks for sharing the article.

1 Like