I have a REST API (distributed with across multiple hosts/containers) that uses MongoDB as a database. Among the collections in my database, I want to focus on the Users
and Games
collections in this example.
Let's say I have an endpoint (called by the user/client) called /join_game
. This endpoint will step-through the following logic:
- Check if game is open (query the
Games
model) - If the game is open, allow the user to join (continue with below logic)
- Add player to the participants field in the
Games
model and update that document - Update some fields in the
Users
document (stats, etc.)
And let there be another endpoint (called by a cron job on the server) called /close_game
which steps-through the following logic:
- Close the game (update the
Games
Model) - Determine the winner & update their stats (update in the
Users
model)
Now I know that the following race condition is possible between two concurrent requests handled by each of the endpoints:
- request A to
/join_game
called by a client -/join_game
controller checks if game is open (it is so it proceeds with the rest of the endpoint logic) - request B to
/close_game
called internally by the server -/close_game
controller sets the game as closed within the game's document
If these requests are concurrent and request A is called before request B, then the remaining /join_game
logic potentially might be executed despite that the game is technically locked. Now this is obvious behavior I don't want and can introduce many errors/unexpected outcomes.
To prevent this, I looked into using the transactions API since it makes all database operations within the transaction atomic. However, I'm not sure if transactions actually solve my case since I'm not sure if they place a complete lock on the documents being queried and modified (I read that mongodb uses shared locks for reads and exclusive locks for writes). And if they do put a complete lock, would other database calls to those documents within the transaction just wait for those transactions to complete? I also read that transactions abort if they wait after a certain period of time (which can also lead to unwanted behavior).
If transactions are not the way to go about preventing race conditions across multiple different endpoints, I'd like to know of any good alternative methods.
Originally, I was using an in-memory queue for handling these race conditions which seemed to have work on a server running the REST API on a single node. But as I scale up, managing this queue amongst distributed servers will become more of an issue so I'd like to handle these race-conditions directly within mongo if possible.
Thanks!