You have probably heard of mysql, postgresql, or sqlite. But have you heard of MongoDB? It is an open-source, non-relational database management system (DBMS) that uses flexible documents instead of tables and rows to process and store various forms of data. In this blog post, we will be looking at a situation called a Race Condition in MongoDB.
Now, what is a Race Condition? "A race condition arises in software when a computer program, to operate properly, depends on the sequence or timing of the program's processes or threads (Wikipedia)." Basically, a race condition is an event that locks software from working as intended due to a corruption of a saved state or memory.
Creating a Race Condition in MongoDB
Race conditions can occur in MongoDB because of bad queries. We will dig into that with an elaborate example. Let's say we are creating a game-server project that does the following.
1. Connects to MongoDB
2. Provides an API for players to join a room with a maximum capacity of 4 players
Step 1: Setting up the Project
I am using nodejs for this test. You can install nodejs and npm on your OS using any method you like. Set up a basic nodejs project with
npm init and execute the code below under the projects directory.
Create a typescript config file
Then, create a directory called src. Under it, create a mongoose schema called
room and create a typescript file
room.schema.ts. It should contain a schema with
players in it. To update the room's value to full if it reaches max players, we create another field named
We can also create another field named
count which we will use later.
Step 2: Coding the API
Next, create an
index.ts file using express. Then, establish a connection to mongoose with an API that searches for a room that is not full. If found, it should add a player to it; otherwise, it should create a new room to host that new player. Think of it as a room of Ludo, where a maximum of 4 players can join, and new rooms need to be created if more players want to join.
Notice that I created an API on the route
/race. The code looks like it should work. Let's run it.
Step 3: Testing
If we go to the URL,
http://localhost:3000/race it will hit the
/race section API code. First, it will create a random string for "userId", then search for a room that is not full. If all created rooms are full, it will create a new room. If we go to that URL again, a room should already exist with a player. So, we will find that room and be added as another player.
Repeat this a total of 8 times. If you query your MongoDB database, you will find 2 rooms with 4 players each and
isFull set to true for both rooms.
So, everything works, right? Where is the race condition I was blabbering about?
The thing is, this is not a real case scenario. Let's suppose 20 players concurrently hit your API; how will it work then? Let's try clearing the MongoDB database and then executing this code.
This script uses the apache-benchmark tool to send 20 requests through 20 concurrent processes to simulate 20 players hitting the API concurrently.
It looks like my code put the first 19 players in one room and then 1 player in another room. Why did this happen? How to solve it? We will be discussing this next time, so tune in to find out.
This is Part 1 of understanding and fixing Race Conditions in MongoDB. Read Part 2 here.