Charlie Schliesser
Charlie Schliesser

Reputation: 8237

How can I prevent Mongoose from allowing multiple updates of a document across parallel requests?

Given the schema:

{
    widgets: [{
        widget: {type: mongoose.Schema.Types.ObjectId, ref: 'widget'},
        count: {type: Number}
    }]
}

and an application route where:

  1. User attempts to add a new widget into the widgets array of an existing document.
  2. We check that the widget being added meets some requirements.
  3. We add the widget to the document and save the document.

In this setup, if I have 2 parallel requests to perform the same action, both checks in number 2 pass and the document is saved with 2 copies of the widget, even though that's "illegal" in my business logic.

The async flow looks like:

  1. Req1 hits route, loads existing doc
  2. Req2 hits route, loads existing doc
  3. Req1 checks doc conditions
  4. Req2 checks doc conditions
  5. Req1 embeds the new widget, saves doc
  6. Req2 embeds the new widget, saves doc

I thought that document versioning (__v) would solve this for me as it has in the past, but apparently I never understood this to begin with because both requests are running sequentially and my debugger shows that the version of the document is X in both pre-save states, and X+1 in the post-save state. I don't understand why that doesn't throw a version error.

I think that this is an asynchronous problem to solve, not necessarily strictly Mongoose, and have tagged as such.

Edit: This works but seems remarkably verbose:

model
    .findOneAndUpdate({
        _id: doc._id,
        __v: doc.__v
    },
    {
        $push: {
            widgets: {
                widget: widget_id,
                qty: 1
            }
        },
        $inc: {
            __v: 1
        }
    },
    function(err, doc) {
        // ...
    });

Also, it is unfortunate that I can't alter my existing doc and then run the save() method on it.

My searching found this bug where the versionKey didn't increment automatically when using this method. I suppose I really don't understand versionKey properly!

Upvotes: 0

Views: 1014

Answers (2)

Lisa Gagarina
Lisa Gagarina

Reputation: 693

Then maybe you can try to add widgets check in update query:

model.findById(doc._id, function(err, doc) {
  // handle error

  // on update query check if there is no such widget in widgets array
  doc.update(
    { _id: doc._id, { 'widgets.widget': { $nin: [widget_id] } }},
    { 
      $push: { 
        widgets: { widget: widget_id, count: widget_count } 
      }
    }
  );
});

Upvotes: 0

Lisa Gagarina
Lisa Gagarina

Reputation: 693

Have a look at this explanations of versionKey property (if you haven't already)

http://aaronheckmann.tumblr.com/post/48943525537/mongoose-v3-part-1-versioning

The example from the article looks similar to yours except that they are modifying array items (comments) and you are pushing new item to widgets array. But as I understood if you use mongoose v3+ and perform save() operation, this will do all necessary operations with versionKey for you.

So if you will do something like:

model.findById(doc._id, function(err, doc) {
  // handle error
  doc.widgets.push({ widget: widget_id, count: widget_count });
  doc.save(callback);
});

then save() operation should internally looks like this:

doc.update(
  { _id: doc._id, __v: doc.__v },
  { 
    $push: { 
      widgets: { widget: widget_id, count: widget_count } 
    },
    $inc: {
      __v: 1
    }
  }
);

So maybe you should make sure you use mongoose v3+ and do push() and then save() or is it how you did this stuff initially?

I haven't tested this, just wanted to share my search results and thoughts with you in case this can help somehow.

Upvotes: 1

Related Questions