Update only if validation success

24 views
Skip to first unread message

Zoro

unread,
Jul 27, 2016, 8:34:22 PM7/27/16
to mongodb-user
Hello

I want update a document only when some expression success. For example, i have an accounts collection next structure:
{
   
"_id": ObjectId("5795db397e92fa345def9522"),
   
"name": "Simon",
   
"history": [
     
{
         
"change": -50,
         
"when": 1469536987003
     
}
     
{
         
"change": -300,
         
"when": 1469536987002
     
}
     
{
         
"change": 100,
         
"when": 1469536987001
     
}
     
{
         
"change": 500,
         
"when": 1469536987000
     
}
   
]
}

And before push next history item, i need check that the sum of change positive.

I can get document, check the sum and then update. But it can lead to race condition error, when another process push negative change.

How can i implement this?

p.s. I know about two phase commit, but it seems too complicated. 

Amar

unread,
Aug 5, 2016, 3:19:57 AM8/5/16
to mongodb-user

Hi Zoro,

In MongoDB, a write operation on a single document is atomic even it modifies multiple items within that document. However, in the case that you mentioned, getting the sum of change requires an aggregation with $unwind and $group which cannot be done inside the update.

One way to do this, you could have a precomputed sum on the top-level of the document which reflects the current total on the top-level of the document, for example:

{
   "_id": ObjectId("5795db397e92fa345def9522"),
   "name": "Simon"
,
   "sum": 250

   "history": [
      {
         "change": -50,
         "when": 1469536987003
      }
      {
         "change": -300,
         "when": 1469536987002
      }
      {
         "change": 100,
         "when": 1469536987001
      }
      {
         "change": 500,
         "when": 1469536987000
      }
   ]
}

This way, your update operation could specify the {sum : {$gt : 0}} without having to run aggregation. You should keep in mind that the application has to update the sum field when new entries are added to history.

Another way to control concurrency is through some form of Two Phase Commit. You could implement a simple version by using findAndModify to update a field to indicate that the record is being updated (preventing other operations to update this document) and get the most up-to-date document, make your update and then change the field again to a different state so that other operations can update the document.

One final thing to take into consideration is the possibility that with ever-increasing entries in history the document might hit the 16MB Document Size Limit. In that case, you could consider moving the history to a separate collection and only keeping the pre-aggregated sum in the main collection.

Regards,

Amar


Reply all
Reply to author
Forward
0 new messages