nbw/figmex

Global state: better solution?

mxgrn opened this issue · 2 comments

mxgrn commented

Reading in Figmex.StateStore:

Configured with a "global" name, so one instance exists across all nodes. This isn't my favorite solution, but minimizes race conditions for now.

Any hint on what would be a more optimal solution? Maybe something based on mnesia? Thanks!

nbw commented

@mxgrn Hey Max. You're actually spot on with what I was imagining. Mnesia, as I understand it, supports some transaction locking features that would make it a good candidate. I sort of talked about going that direction here, but the main reason I didn't was time and effort. I couldn't find someone who had done it with Fly.io and I didn't want to spend the time figuring it out since it was just a proof of concept.

If the goal is to stay 100% in the Elixir/Erlang eco-system then Mnesia is seemingly a good place to start. Otherwise if one might venture beyond Elixir, then maybe Redis, but anything outside of elixir will require money and more infrastructure.

You need some way of reliably saving a snapshot of the board in a distributed way. Also make sure that anyone that joins the "room" isn't receiving stale data. I don't think there's a perfect solution, just solutions that are tolerable.

Note: Mnesia seems like the choice since Elixir's Ets is not distributed so it wouldn't work there.

Why the current solution is not great

I think it's just worth mentioning why I think a "global" genserver isn't good:

  • One "global" genserver, but many distributed nodes: depending on where you are in the 🌏 world, you might be really close or really far from the node that is actually hosting the "global" genserver. you might have really good or really bad response time
  • it doesn't really scale well as a solution. in a real product where there were many rooms for many teams and users, you'd want to dynamically create gen servers on the fly. you can still communicate with a genserver on another node if you have the PID (and Node ID0, so you don't have to register it "global" for it be accessible. You just need to make sure you have another way of finding it.

Why the current solution is good

  • Very easy to implement
  • We avoid most race conditions because there is one instance and no syncing required
    • I say "most" because if two people clicked the same thing at the exact same time, the person who is physically closer to the node hosting the global genserver would win (I suspect). So it's not bulletproof.
mxgrn commented

Thanks for the extended answer! I'm considering leveraging Mnesia for my multiplayer memory game, which currently relies on Redis to store the game state. As I'm rewriting it from scratch, I found it a fun goal to get rid of that dependency. My quick experiments with 2 linked BEAM nodes show that it works nicely for my case (I also used the mnesiac lib, btw), especially I like the fact that I can reboot those nodes one by one during deployments - and not lose the state.

However, a turn-based game like this is a much simpler use case for mnesia, as there's no room for race conditions (only 1 player is changing the state at any given moment).