Maintaining Atomicity When Using Save Middleware

164 views
Skip to first unread message

Greg Pulier

unread,
Apr 8, 2015, 10:29:23 PM4/8/15
to mongoo...@googlegroups.com
Hi,

I am looking to prevent multiple simultaneous saves from crossing a (variable) threshold.
More specifically consider a schema which is tracking Jobs which can allow a certain number of Workers like this:

var JobSchema   = new mongoose.Schema({
        positionsopen : Number,
        workersaccepted:{type:Number,default:0},
        locked: {type:Boolean, default:false}
});

The goal is that the first worker to accept the job causes the workersaccepted to increment by 1.
The next worker to accept the job also causes the the workersaccepted to increment by 1.
As soon as someone accepts the job and causes workersaccepted to equal positionsopen I would like to set locked = true and prevent any further saves from succeeding.

My approach is to use Mongoose Middleware as follows:

JobSchema.pre('save', function(next) {
    if (this.locked) {
        next('Error: All Positions Filled');
    }else{
        if (this.workersaccepted >= this.positionsopen) this.locked = true;
    }
    next();
});

This works perfectly for simple tests but does anyone know if this will remain atomic when huge numbers of simultaneous saves occur?
It seems to me it will NOT work since multiple saves maybe be at the point at which they have determined that workersaccepted is less than positionsopen and that it is safe for them all to continue with their saving.  In this case the document will end up with more workersaccepted than positionsopen which is unacceptable.
If save is atomic even with this type of middleware function than I guess this will work.  I would assume Mongoose would have to be smart enough to use the "Update if Current pattern" or versioning or some method of ensuring atomicity.

Does anyone have any thoughts on this or suggestions for a better way?

Thank you!
-Greg

Jason Crawford

unread,
Apr 8, 2015, 11:57:47 PM4/8/15
to mongoo...@googlegroups.com
You are right to spot a problem here.

The $inc (increment) operator is atomic: http://docs.mongodb.org/manual/reference/operator/update/inc/

But that doesn't do the test you're looking for. For that, you might want to use a trick where you put a condition on the update, as described here: http://stackoverflow.com/questions/14463189/testing-and-decrementing-in-an-atomic-operation

General info on atomicity in Mongo for reference:

Hope that helps,
Jason

--
Blog: http://blog.jasoncrawford.org  |  Twitter: @jasoncrawford


--
Documentation - http://mongoosejs.com/
Plugins - http://plugins.mongoosejs.com/
Bug Reports - http://github.com/learnboost/mongoose
Production Examples - http://mongoosejs.tumblr.com/
StackOverflow - http://stackoverflow.com/questions/tagged/mongoose
Google Groups - https://groups.google.com/forum/?fromgroups#!forum/mongoose-orm
Twitter - https://twitter.com/mongoosejs
IRC - #mongoosejs
---
You received this message because you are subscribed to the Google Groups "Mongoose Node.JS ODM" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mongoose-orm...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

nwhite

unread,
Apr 9, 2015, 12:36:31 AM4/9/15
to mongoo...@googlegroups.com

The pre save is not going to help you with atomicity.


// worker to get a job
Job.findOneAndUpdate(
  {locked: false, $where: 'this.positionsopen < this.workersaccepted'}, // condition
  {$set {locked: true}, $inc: { positionsopen: 1 }} // update
  {new: true}, // options  (new returns the modified version, false= orginal before update)
  function(err, doc){
     // do something
  });

Job.findOneAndUpdate(
  {locked: true, _id: '<job_id>'},
  {$set: {locked: false}, $inc: { positionsopen: -1}},
  {new: true},
  function(err, doc){

  });

Note: the $where condition is not ideal but would get the job done.

--
Reply all
Reply to author
Forward
0 new messages