Mongodb queries from multiple processes; how to implement atomicity?

Viewed 1852

I have a mongodb database where multiple node processes read and write documents. I would like to know how can I make that so only one process can work on a document at a time. (Some sort of locking) that is freed after the process finished updating that entry.

My application should do the following:

  • Walk through each entry one by one with a cursor.
  • (Lock the entry so no other processes can work with it)

  • Fetch information from a thirdparty site.

  • Calculate new information and update the entry.
  • (Unlock the document)

Also after unlocking the document there will be no need for other processes to update it for a few hours.

Later on I would like to set up multiple mongodb clusters so that I can reduce the load on the databases. So the solution should apply to both single and multiple database servers. Or at least using multiple mongo servers.

3 Answers

Encountered this question today, I feel like it's been left open,

First, findAndModify really seems like the way to go about it, But, I found vulnerabilities in both answers suggested:

in Treefish Zhang's answer - if you run multiple processes in parallel they will query the same documents because in the beginning you use "find" and not "findAndModify", you use "findAndModify" only after the process was done - during processing it's still not updated and other processes can query it as well.

in arboreal84's answer - what happens if the process crashes in the middle of handling the entry? if you update the version while querying, then the process crashes, you have no clue whether the operation succeeded or not.

therefore, I think the most reliable approach would be to have multiple fields:

  • version
  • locked:[true/false],
  • lockedAt:[timestamp] (optional - in case the process crashed and was not able to unlock, you may want to retry after x amount of time)
  • attempts:0 (optional - increment this if you want to know how many process attempts were done, good to count retries)

then, for your code:

  1. findAndModify: where version=oldVersion and locked=false, set locked=true, lockedAt=now
  2. process the entry
  3. if process succeeded, set locked=false, version=newVersion
  4. if process failed, set locked=false
  5. optional: for retry after ttl you can also query by "or locked=true and lockedAt<now-ttl"

and about:

i have a vps in new york and one in hong kong and i would like to apply the lock on both database servers. So those two vps servers wont perform the same task at any chance.

I think the answer to this depends on why you need 2 database servers and why they have the same entries,

if one of them is a secondary in cross-region replicas for high availability, findAndModify will query the primary since writing to secondary replica is not allowed and that's why you dont need to worry about 2 servers being in sync (it might have latency issue tho, but you'll have it anyways since you're communicating between 2 regions).

if you want it just for sharding and horizontal scaling, no need to worry about it because each shard will hold different entries, therefore entry lock is relevant just for one shard.

Hope it will help someone in the future

relevant questions:

Related