0

Suppose that I have a document

{
  maxCountAllowed:100,
  currentCount:99
}

And I have an API that users call to increment the currentCount if it's not 100 yet ..

if(document.currentCount < document.maxCountAllowed){
    document.currentCount ++;
    document.save();
}else{
    console.log("sorry maximum count reached");
}

Suppose that 2 or more users called the API at the same time .. both of them will find that the currentCount is still 99 so the function will proceed and both of them will increment the currentCount to be 101 or more .. how can I prevent something like this ? Even if both of them will make it a 100, I still want only one of them to be able to update and the other one gets the error message.

You Ma
  • 189
  • 1
  • 12

3 Answers3

3

Mongo provides document level atomicity as it is described here: https://docs.mongodb.com/v3.4/tutorial/update-documents/#behavior It means that if there are two concurrent update operations on an object, then they will be executed consecutively.

Also update operations support $inc operator: https://docs.mongodb.com/manual/reference/operator/update/inc/#up._S_inc

And the last part is $where operator: https://docs.mongodb.com/manual/reference/operator/query/where/#examples Keep in mind that it is not as performant as querying with constants.

So you could update only conforming documents:

db.products.update(
  { $where : "this.currentCount < this.maxCountAllowed" },
  { $inc: { currentCount: 1 } }
);

Then check the result of the operation to find out if anything was updated.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
vsenko
  • 1,129
  • 8
  • 20
  • So if there are 2 concurrent updates in the same document they will happen synchronously.. one of them will update the document while the other one updates 0 documents ? – You Ma Oct 27 '17 at 18:31
  • 1
    Mongo will handle them consequtively and only one of them will update the document. – vsenko Oct 28 '17 at 14:39
1

You can perform this operation on the database as an update with additional query condition. So firstly you have to fetch your document to know the limit. Then let's suppose that your document's _id is equal to 1, so you can execute such update:

db.yourCollection.update({_id: 1, currentCount: {$lt: 100}}, { $inc: { currentCount: 1 } });

$inc will do the incrementation but only if current state of document has currentCount field with value less than 100, which is your limit defined in another field of this document.

mickl
  • 48,568
  • 9
  • 60
  • 89
  • so in this case only one o f them will increment it while the others won't get the message in the part of the console.log but will have the mongo error .. am I right ? – You Ma Oct 27 '17 at 18:11
  • 1
    No, there's gonna be an empty update because none of your documents will match specified conditions. You can also check updateAndModify if you want to retrieve modified document. – mickl Oct 27 '17 at 18:13
1

I think the case you are talking about can never happen. Imagine there will be 2 requests at the same time, they will both get 99/100 as result. Then they will both try to update to 100/100. You are incrementing the value you get from database in node script, not the value in mongo directly.

You could also use db.collection.findAndModify, but it looks like there is no mongoose API for this.

https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/

Martin Adámek
  • 16,771
  • 5
  • 45
  • 64
  • OK .. even if it's 100 I still cannot return to 1 user that he updated it, while the others gets a message in the console.log part – You Ma Oct 27 '17 at 18:13
  • 1
    Then you should try the `findAndModify` method. Check this question on how to use it with mongoose: https://stackoverflow.com/questions/7334390/does-mongoose-support-the-mongodb-findandmodify-method – Martin Adámek Oct 27 '17 at 18:14