Handling concurrent writes: 101 - manual locking
You may have heard of locking mechanisms to achieve consistent updates in the databases. Here in this blog, I’ll illustrate one of the simplest forms of this approach. A handful of NoSQL database do not offer explicit row level locking out of the box.
Imagine the following case:
User A, B and C attempts to modify (increment) a counter at the same time. At a given point of time, if the DB holds the counter value ‘5’, it is expected to become ‘8’. Multiple parties cannot work with the same value and save the same value at the same time. If A, B , and C retrieves the value ‘5’, and they all save ‘6’, the data loses its integrity.
To handle this, we introduce the following:
Each time, when an update should happen, if the boolean flag is ‘false’, it should be set to ‘true’ and be saved immediately. And then, rest of the actual modification should be done (in our case, modifying the value and saving the updated value).
Any other party attempting to update the value, will first read the ‘current’ locked state, and will fallback to a retry mechanism for a locked row.
I am adding some code examples to illustrate this process better:
async incrementCounter(): Promise<number> {
// Find the counter document
const counter = await Counter.findOne();
if (!counter) {
throw new Error('Counter document not found');
}
// Attempt to acquire a lock
if (!counter.locked) {
counter.locked = true;
await counter.save();
// Perform the update within a try-catch block to handle potential conflicts
try {
counter.value++; // Increment the counter
await counter.save(); // Save the updated counter value
} catch (error) {
// Handle conflict (e.g., another concurrent write)
console.error('Concurrency conflict:', error.message);
}
// Release the lock
counter.locked = false;
await counter.save();
} else {
console.log('Another operation is already in progress. Try again later.');
}
return counter.value;
}
The implementation can further be enhanced with an appropriate retry mechanism for the ones that failed to lock on the resource.