Sorry for the title, I did not know what to write.
I’m trying to build an architecture for a game. In the game, there are
starbases which are basically event processors: they have a state,
receive different events and update their state accordingly.
I do not want to have a process per starbase because it will be many
of them, and they are idle most of the time. For example, they have
production lines: when a production of food is started, I calculate
the time of the production end, and I will send an event to the
starbase at this time. Durations can be in minutes so I have no use
of idling processes for several minutes. Also states take memory.
However I want all events for a given starbase to happen sequentially,
because I will load the state from database, handle the event, and
write the state.
So, what I plan to do is to have a worker pool and use
erlang:phash2/2 to hash the id of my starbase and send all events
for a given starbase to the same worker. This should be enough to
prevent concurrent read/write of the same state and work sequentially.
(A small word on distribution : if I have to work on different nodes,
those would represent solar systems, or solar system clusters, so a
given starbase will always be handled on the same node.)
If you think this is a bad design, i think you can skip the rest of
this post, and please tell my why.
Now, I have to update several starbase at a time, for example to
conclude a trade. Starbases declare what they need to but and what
they want to sell. I have some code running to figure out matching
orders. When I match is found, I want to run a task on a worker to
load both states, check if orders are still matching, update states
(mark orders as fullfilled, exchange money, move goods from the
“selling” cargo to the “sold-and-waiting-to-be-carried” cargo space).
The problem is that this task will run on the worker for starbase A,
and starbase B could be concurrently updated with another event that
will consume goods in cargo.
I am looking for a solution that could handle tasks for any amount of
starbases, not just 2.
So this is my current plan:
- Hash all the keys (starbase ids) to find the workers. If all keys
give the same worker, just run the task as a single-key task.
- With multiple workers, fetch the pid of the workers, take one pid
:masterand other pid(s) as
- Run an inner task on the master worker: pass the slave keys
(starbase ids) to it, then the worker will wait for an
with those keys and the slave pids from all slaves.
- Run an inner task on each slave worker: pass the master pid, the
worker will send the
ackmessage with the slave key to it. Then,
wait for a
:finishedmessage. All messages will be tagged with
the same ref to identify the event and have selective receives.
- At this point, states cannot be changed by other events.
- When the master received all acknowledgements from the slaves, run
the actual task (i.e. load states, handle the event, write states),
then send the
:finishedmessage to all slave pids.
It seems a bit complicated, no ? The main problem is to make workers wait doing nothing to provide locking.
I have another approach with a mutex I wrote that can lock multiple
keys without deadlocks, but that would require to lock the keys for
all tasks, even the single-key ones, so a lot more message passing and
a bottleneck on the mutex. I expect to have way more events for a
single state than events involving multiple entities.