Skip to content

Instantly share code, notes, and snippets.

@mpalmerlee
Last active April 2, 2018 21:12
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mpalmerlee/9709085 to your computer and use it in GitHub Desktop.
Save mpalmerlee/9709085 to your computer and use it in GitHub Desktop.
Save MongoDB Document With Concurrent Edit Protection
var mongoose = require("mongoose");
var ObjectId = mongoose.Schema.Types.ObjectId;
//create schema for a post
var PostSchema = new mongoose.Schema({
nonce: ObjectId, //this is used for protecting against concurrent edits: http://docs.mongodb.org/ecosystem/use-cases/metadata-and-asset-management/
name: String,
dateCreated: { type: Date, default: Date.now },
dateLastChanged: { type: Date, default: Date.now },
postData: mongoose.Schema.Types.Mixed
});
//compile schema to model
exports.PostModel = db.model('post', PostSchema);
var postService = require('./postService');
exports.AppendPostText = function(postId, text, callback){
saveGameByIdWithConcurrencyProtection(postId, function(doc, cb){
doc.postData.text += text;
cb(null, data);
}, 0, function(err, doc){
callback(err, doc);
});
};
var mongoose = require("mongoose");
var models = require("./documentModel");
/*
This method uses the nonce field in the document to protect against concurrent edits on the post document
http://docs.mongodb.org/ecosystem/use-cases/metadata-and-asset-management/#create-and-edit-content-nodes
transformFunction will be given the post doc and should callback the data to update:
transformFunction(doc, callback)
*/
var savePostByIdWithConcurrencyProtection = function(postId, transformFunction, retries, callback){
FindPostById(postId, function(err, doc){
if(err || !doc){
callback(err || "Unable to find post in savePostByIdWithConcurrencyProtection");
return;
}
transformFunction(doc, function(err, data){
if(err){
callback(err);
return;
}
data.nonce = new mongoose.Types.ObjectId;
//setTimeout(function(){ //setTimeout for testing concurrent edits
models.PostModel.update({"_id":postId, "nonce":doc.nonce}, data, function(err, numberAffected, raw){
if(err){
callback(err);
return;
}
//console.log("savePostByIdWithConcurrencyProtection: ", numberAffected, raw);
if(!numberAffected && retries < 10){
//we weren't able to update the doc because someone else modified it first, retry
console.log("Unable to savePostByIdWithConcurrencyProtection, retrying ", retries);
//retry with a little delay
setTimeout(function(){
savePostByIdWithConcurrencyProtection(postId, transformFunction, (retries + 1), callback);
}, 20);
} else if(retries >= 10){
//there is probably something wrong, just return an error
callback("Couldn't update document after 10 retries in savePostByIdWithConcurrencyProtection");
} else {
FindPostById(postId, callback);
}
});
//}, 5000);
});
});
};
var FindPostById = function(id, callback){
//search for an existing post
models.PostModel.findOne({"_id":id}, function(err, doc){
if(err){
console.error("FindPostById:", err);
} else if(!doc){
var msg = "Could Not FindPostById:" + id;
console.error(msg);
return callback(msg);
}
callback(err, doc);
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment