Pessimistic Locking with MongoDB - codecentric AG Blog

:

In this article, I’m going to sketch a pattern for implementing pessimistic locking with MongoDB. MongoDB is a document-orientated NoSQL datastore that does not support locking itself.

In some business processes it may be required that you have an exclusive access to a single document. That’s when you need something like pessimistic locking.

Requirements

The implementation should offer the following features:

  • Locking does not change the locked document itself
  • Locks can be removed manually or by a timeout

For the rest of this article, let’s assume we want to lock documents in a collection called workitem.

Schema Design

The basic idea is to maintain a separate collection holding the locks, e.g. workitem.lock. The schema of a lock may look like this:

{
        "_id" : ObjectId("5087b72181a445980ae47d13"),
        "ts" : ISODate("2012-10-24T09:38:41.173Z"),
        "aid" : "my_app_my_session_id_4711"
}

{ "_id" : ObjectId("5087b72181a445980ae47d13"), "ts" : ISODate("2012-10-24T09:38:41.173Z"), "aid" : "my_app_my_session_id_4711" }

  1. _id: The _id of the locked document
  2. ts: Timestamp of the lock
  3. aid: Some id that unambiguously identifies the context of the application (it may be a session id, process id etc.)

Let’s set up the locking collection:

db.workitem.lock.drop();
db.createCollection("workitem.lock");
db.workitem.lock.ensureIndex( { ts: 1 }, { expireAfterSeconds: 30 } );
db.workitem.lock.ensureIndex( { aid: 1 }, {unique: false} );

db.workitem.lock.drop(); db.createCollection("workitem.lock"); db.workitem.lock.ensureIndex( { ts: 1 }, { expireAfterSeconds: 30 } ); db.workitem.lock.ensureIndex( { aid: 1 }, {unique: false} );

I’ll give a detailed explanation of the indexes later on.

So we end up with the following collections:

> show collections
system.indexes
workitem         // collection for domain objects
workitem.lock    // collection for locks on workitem documents

> show collections system.indexes workitem // collection for domain objects workitem.lock // collection for locks on workitem documents

Obtaining a Lock

Obtaining a lock consists of the following steps:

  1. Query your document doc (depending on the size of the document and complexity of the query it may be better to read the _id only or the whole document)
  2. Create the application id app_id. The application is free to choose the scope of this id. It may be the session id of a web application.
  3. Insert a lock {_id: doc._id, ts: <current timestamp>, aid: app_id}. The insert is an atomic operation. If it runs, your application successfully set the lock. If not, someone else already has the lock to your document.

Releasing a Lock Manually

Perform these steps to manually release a lock:

  1. Modify your locked document
  2. Delete the lock either by
    • _id: when the scope of the lock was a single document.
    • aid: when your application locked more than one document. Another use case may be when you used a session id of a web application. Assume the user closed the browser but the lock is still active. If you have some kind of session listener in your application server and a session timeout occurs, you can still release all locks belonging to that session.

    If the delete fails, someone else has already removed the lock. You can react to that be re-reading the document and comparing the versions etc.

Let MongoDB release the Lock

Since version 2.2 there is a new kind of index, a TTL index. It’s basically an index on a date key where you can specify a time to live (TTL) of documents inside a collection. That is exactly what we did when defining the index on the ts key:

db.workitem.lock.ensureIndex( { ts: 1 }, { expireAfterSeconds: 30 } );

db.workitem.lock.ensureIndex( { ts: 1 }, { expireAfterSeconds: 30 } );

MongoDB periodically checks the age of the documents and purges them from the collection (using a hearbeat of once per second).

If your application fails to remove locks, this TTL index kicks in as a fallback. The TTL value should by chosen appropriate. TTL indexes do not work on capped collections.

Summary

In case you need pessismistic locking inside your MongoDB application this article gives you some hints on how to implement it.

Of course, all applications accessing the collection with documents to lock will need to use the same strategy.

I’m looking forward to your pessimistic locking implementations. Let me know when you have build something cool.