8

I've a big logic problem using Node/Mongoose/Socket.io ... Let's say I got a Server model which is often called at the same time in my application, some calls involve updating datas in the model.

    db.Server.findOne({_id: reference.server}).exec(function(error, server) {

        catches.error(error);

        if (server !== null) {

              server.anything = "ahah";

              server.save(function(error) { });

        }

    }

Sometimes, 2 people will call this at the same time, while the first person will save() the other guy could already have findOne the "server" and got the "old object" which isn't up-to-date and save() it.

The big problem here is when the second guy will save() the "server" (the "old object") it will literally overwrite the changes of the first one ... You can imagine the big conflicts it will create on my application.

I thought about changing all the save() methods to update() which get rid of the problem but at some point in the project it's very tricky to use the update() directly, and not as practical.

Is there a way to "lock" the findOne() call while someone is updating it ? Like when you findOne() you also say "hey i will update this soon so don't let people find it right now" (with Mongoose, or even MongoDb)

It's been a while i'm searching i don't find any answer :(

Hope you understood my problem ;) thank you !

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
Laurent
  • 2,284
  • 2
  • 21
  • 41
  • 1
    Nope. See http://stackoverflow.com/questions/11076272/its-not-possible-to-lock-a-mongodb-document-what-if-i-need-to – JohnnyHK Aug 30 '14 at 03:40
  • then how can you build a website with people on it with mongo ? ... – Laurent Aug 30 '14 at 03:41
  • You structure your schema so that all of your doc modifications can be done using atomic `update` calls. – JohnnyHK Aug 30 '14 at 03:42
  • Tricky ... I still don't understand what the aim of Mongoose if they aren't able to manage this kind of situation, the save() system is useless if there are 2 people that are doing similar actions which will happen for sure on every website ... Thanks for your reply, i'll refacto everything ;) – Laurent Aug 30 '14 at 03:49
  • 1
    Keep in mind that Mongoose does implement `save` using an atomic `update` call that only `$set`s the fields in your doc that you've actually changed so it's not as bad as you're probably thinking. It's array manipulation that gets you in the most trouble (which is why Mongoose added [versioning](http://aaronheckmann.tumblr.com/post/48943525537/mongoose-v3-part-1-versioning)), but you need to be conscious of what's going on regardless. – JohnnyHK Aug 30 '14 at 13:46
  • Wait ... If it does change only the fields that i've changed in the object why is it overwriting the whole object ? That's exactly my problem right now : there''s a data that's changed, and another guy change another data of the same object at the same time, and everything is "overwritten" ... Mongoosse doesn't seem to work the way you say ? You know certainly better than me but then I don't get it :( – Laurent Aug 30 '14 at 14:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/60296/discussion-between-johnnyhk-and-laurent). – JohnnyHK Aug 30 '14 at 14:23
  • Did you find a satisfying solution? – alexislg Feb 09 '15 at 07:25
  • 1
    this needs more attention from beginner MEAN developers. – Sunny R Gupta May 05 '16 at 05:15

2 Answers2

4

As you can tell here, this is not the best way to handle processing updates on your data. If you consider what you are asking to do it essentially boils down to :

  1. Fetch an object from the database.
  2. Update a property in code.
  3. Save that data back with no guarantee something else modified it.

So where possible you need to avoid that pattern and follow the common sense that you just need to change an existing value where it is presently not set to that value. So this means just processing an "update" type of statement with an operator such as $set:

db.Server.findOneAndUpdate(
    { "_id": refernce.server, "anything": { "$ne": "ahah" } },
    { "$set": { "anything": "ahah" } },
    function(err,server) {
       if ( server != null ) {

          // then something was actually found and modified
          // so server now is the updated document

       }
    }
);

This means you are throwing away any field validation or other save hooks for mongoose, but it is an "atomic" form of update in that reading and writing are not separate operations, which is how you are currently implementing.

If you are looking to implement some type of "locking" then a similar approach is your best way to do this. So if you want to set a "state" on a document to show that someone is currently editing it, then maintain a field to do so and build it into your queries.

For "reading" a document and getting the information that you want to present to an "edit" then you would do something like this:

db.Server.findOneAndUpdate(
    { "$_id": docId, "locked": false },
    { "$set": { "locked": true } },
    function(err,document) {

    }
);

Which means as someone "grabs" the edit then subsequent operations would not be able to do so since they are looking to retrieve a document whose locked state is false, and it no longer is. The same principle applies when committing your edit as a "save", just in reverse:

db.Server.findOneAndUpdate(
    { "$_id": docId, "locked": true },
    { "$set": { "locked": false } },
    function(err,document) {

    }
);

You can always do more advanced things such as saved revisions or expecting a version number with operations or any other form of handling. But generally speaking, you should be managing this yourself according to your needs

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • The only problem with the last solution is that if the other guy really need to change the data "after" the first one changed it, the "locked" state will cut the process and that can be make other conflicts .. A good trick anyway, i'll change my code and try to use mongo this way ;) – Laurent Aug 30 '14 at 04:38
1

I just realized I posted a similar post on stack overflow. You can find the post here: How to read/write a document in parallel execution with mongoDB/mongoose

In this post someone told me to keep somedate in memory to avoid this behavior. This is what I did and it works great. But if you are using multi process you need to find a way to share memory between processes.

Community
  • 1
  • 1
alexislg
  • 1,018
  • 13
  • 20