Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose optimistic concurrency with version number

Is there any way to force mongoose to always validate document versions on save? As far as I can tell, the default behavior enforces the version number only when modifying arrays in the document. Even worse than that, it seems that adding an element to an array is allowed even when the document versions do not match, so that currently, even if you're modifying an array, you need to replace the array in order to get the version check. (Note that the examples I'm using use schemaless subdocuments (defined simply as "{}") which may be affecting the behavior). Other than this article I can't find any documentation on the topic. Perhaps there's a plugin that does this?

like image 313
joniba Avatar asked Jun 15 '16 07:06

joniba


People also ask

How does Mongoose handle concurrency?

With Optimistic Concurrency The idea behind optimistic concurrency is to track the version of the document and increment the version every time you save() . If the version of the document in the database changed between when you loaded the document and when you save() , Mongoose will throw an error.

What is Version Key in mongoose?

The versionKey is a property set on each document when first created by Mongoose. This keys value contains the internal revision of the document. The name of this document property is configurable.

How do you implement optimistic concurrency?

Optimistic concurrency control transactions involve these phases: Begin: Record a timestamp marking the transaction's beginning. Modify: Read database values, and tentatively write changes. Validate: Check whether other transactions have modified data that this transaction has used (read or written).

How does Mongoose know which collection?

Mongoose uses the model name, as passed when it was created: mongoose. model("User", UserSchema) , converted to lower case and with an 's' appended. For the model User it uses the collection users by default. You can change this by explicitly specifying the collection name in the schema.


2 Answers

Disclaimer: I wrote the plugin myself to tackle this issue in my own code, based on this issue on GitHub.

This is late, but hopefully better than never.

The mongoose-update-if-current plugin might give you the functionality you're looking for. It adds optimistic concurrency control when calling .save() on a document, using the version field. It'll increment __v each time the document is saved, and will prevent different versions from being saved over each other. For example:

// saves with __v = 0
let product = await new Product({ name: 'apple pie' }).save();

// query a copy of the document for later (__v = 0)
let oldProduct = await Product.findById(product._id);

// increments to __v = 1
product.name = 'mince pie';
product = await product.save();

// throws an error due to __v not matching the DB version
oldProduct.name = 'blueberry pie';
oldProduct = await oldProduct.save();

As a result, we now have optimistic concurrency control for Product.

Caveat: The plugin validates the version in .save() only, not in static model methods such as Product.findByIdAndUpdate().

like image 127
Eoin Avatar answered Oct 10 '22 22:10

Eoin


Mongoose 5.10.0 was released with optimisticConcurrency option for schemas.

const userSchema = Schema({
  name: {
    type: String,
    required: true,
  },
  // ...
}, {
  optimisticConcurrency: true,
  // versionKey: 'version' // => Default: __v
});

Probably, you need connection transactions too. Read more at https://mongoosejs.com/docs/api/connection.html#connection_Connection-transaction

const doc = new Person({ name: 'Will Riker' });

await db.transaction(async (session) => {
  doc.rank = 'Captain';
  await doc.save({ session });
  doc.isNew; // false

  // Throw an error to abort the transaction
  throw new Error('Oops!');
},{ readPreference: 'primary' }).catch(console.error);

Source: https://thecodebarbarian.com/whats-new-in-mongoose-5-10-optimistic-concurrency.html

like image 1
Eduardo Cuomo Avatar answered Oct 10 '22 20:10

Eduardo Cuomo