It's not possible to lock a mongodb document. What if I need to?

女生的网名这么多〃 提交于 2019-11-29 23:58:53

Stumbled into this question while working on mongodb upgrades. Unlike at the time this question was asked, now mongodb supports document level locking out of the box.

From: http://docs.mongodb.org/manual/faq/concurrency/

"How granular are locks in MongoDB?

Changed in version 3.0.

Beginning with version 3.0, MongoDB ships with the WiredTiger storage engine, which uses optimistic concurrency control for most read and write operations. WiredTiger uses only intent locks at the global, database and collection levels. When the storage engine detects conflicts between two operations, one will incur a write conflict causing MongoDB to transparently retry that operation."

Hey the only way of which I think now is to add an status parameter and use the operation findAndModify(), which enables you to atomically modify a document. It's a bit slower, but should do the trick.

So let's say you add an status attribut and when you retrieve the document change the status from "IDLE" to "PROCESSING". Then you update the document and save it back to the collection updating the status to "IDLE" again.

Code example:

var doc = db.runCommand({
              "findAndModify" : "COLLECTION_NAME",
              "query" : {"_id": "ID_DOCUMENT", "status" : "IDLE"},
              "update" : {"$set" : {"status" : "RUNNING"} }
}).value

Change the COLLECTION_NAME and ID_DOCUMENT to a proper value. By default findAndModify() returns the old value, which means the status value will be still IDLE on the client side. So when you are done with updating just save/update everything again.

The only think you need be be aware is that you can only modify one document at a time.

Hope it helps.

"Doctor, it hurts when I do this"

"Then don't do that!"

Basically, what you're describing sounds like you've got a serial dependency there -- MongoDB or whatever, your algorithm has a point at which the operation has to be serialized. That will be an inherent bottleneck, and if you absolutely must do it, you'll have to arrange some kind of semaphore to protect it.

So, the place to look is at your algorithm. Can you eliminate that? Could you, for example, handle it with some kind of conflict resolution, like "get record into local' update; store record" so that after the store the new record would be the one gotten on that key?

Answering my own question because I found a solution while doing research on the Internet.

I think what I need to do is use an Optimistic Concurency Control.

It consist in adding a timestamp, a hash or another unique identifier (I'll used UUIDs) to every documents. The unique identifier must be modified each time the document is modified. before updating the document I'll do something like this (in pseudo-code) :

var oldUUID = doc.uuid;
doc.uuid = new UUID();
BeginTransaction();
if (GetDocUUIDFromDatabase(doc.id) == oldUUID)
{
   SaveToDatabase(doc);
   Commit();
}
else
{
   // Document was modified in the DB since we read it. We can't save our changes.
   RollBack();
   throw new ConcurencyException();
}
Yaroslav Stavnichiy

Classic solution when you want to make something thread-safe is to use locks (mutexes). This is also called pessimistic locking as opposed to optimistic locking described here.

There are scenarios when pessimistic locking is more efficient (more details here). It is also far easier to implement (major difficulty of optimistic locking is recovery from collision).

MongoDB does not provide mechanism for a lock. But this can be easily implemented at application level (i.e. in your code):

  1. Acquire lock
  2. Read document
  3. Modify document
  4. Write document
  5. Release lock

The granularity of the lock can be different: global, collection-specific, record/document-specific. The more specific the lock the less its performance penalty.

An alternative is to do in place update

for ex:

http://www.mongodb.org/display/DOCS/Updating#comment-41821928

db.users.update( { level: "Sourcerer" }, { '$push' : { 'inventory' : 'magic wand'} }, false, true );

which will push 'magic wand' into all "Sourcerer" user's inventory array. Update to each document/user is atomic.

Update: With MongoDB 3.2.2 using WiredTiger Storage implementation as default engine, MongoDB use default locking at document level.It was introduced in version 3.0 but made default in version 3.2.2. Therefore MongoDB now has document level locking.

If you have a system with > 1 servers then you'll need a distributive lock.

I prefer to use Hazelcast.

While saving you can get Hazelcast lock by entity id, fetch and update data, then release a lock.

As an example: https://github.com/azee/template-api/blob/master/template-rest/src/main/java/com/mycompany/template/scheduler/SchedulerJob.java

Just use lock.lock() instead of lock.tryLock()

Here you can see how to configure Hazelcast in your spring context:

https://github.com/azee/template-api/blob/master/template-rest/src/main/resources/webContext.xml

oderfla

Instead of writing the question in another question, I try to answer this one: I wonder if this WiredTiger Storage will handle the problem I pointed out here: Limit inserts in mongodb

If the order of the elements in the array is not important for you then the $push operator should be safe enough to prevent threads from overwriting each others changes.

As of 4.0, MongoDB supports Transactions for replica sets. Support for sharded clusters will come in MongoDB 4.2. Using transactions, commit up DB updates will be aborted if a conflicting write occurs, solving your issue.

Transactions are much more costly in terms of performance so don't use Transactions as an excuse for poor NoSQL schema design!

Sounds like you want to use MongoDB's atomic operators: http://www.mongodb.org/display/DOCS/Atomic+Operations

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!