我尝试基于node&amp ;;尝试为后端实现某种Optimist Concurrency Control。蒙戈。
假设如下:
用户收集结构如此
{
"_id" : ObjectId,
"username" : String,
"password" : String,
"firstName" : String,
"lastName" : String,
"userData": {
"comments": []
},
"__occ": Number
}
这样的评论集合
{
"_id" : ObjectId,
"text" : String
}
我的后端定义了一条创建新评论的路线,一旦创建,就会将其ID添加到其创建者的userData.comments数组中。
为了测试这条路线我创建了一个小脚本,它调用路由10次以创建10个注释(使用for循环,没什么特别的)
我开始使用mongoose但是我遇到了并发问题所以我尝试使用本机mongodb驱动程序,因为我读了Mongo operations on a single document are supposed to be atomic
为此,我使用__occ
字段标记文档的状态,并将更新请求修改为(1) query on the _id plus __occ field ; (2) add a {$inc{ "__occ": 1}}
以增加版本号。如果更新失败,我认为我有冲突,所以我重新加载对象并再试一次。 (见下面的代码)
我基本上无法让事情发挥作用,发生各种奇怪的事情。在几次成功迭代(1或2)之后的大部分时间,加载(参见console.log()和" BGCHECK#2.2"标志)返回一个旧的(即低__occ数字)对象,但是下一次更新成功,我的评论数组搞砸了,因为在此期间所做的所有更改都会被覆盖。
在我期待的10条新评论中,一旦循环完成,我就有4或5条(但我没有任何例外,也没有超过10条顶级重试)
我做错了什么?
编辑示例"古怪"当突然一个find()返回一个更老的'宾语。消息' BGCHECK#2.2'我应该在从mongo DB重新加载对象后打印,以获得最新的对象。 values(第一个数字是console.log()的时间戳) 最后4行是有趣的:我有一个5元素数组,已经被find()返回两次然后我以某种方式松开了最后2个条目 当我松开一个元素时,第16行也出现了类似的情况(" 575e500df16b51b3f64994c7")
1465798669701 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994b9 to set => self.__occ = 0 => concurrency error => new object __occ=1 set = (#1 elts, ["575e500df16b51b3f64994b8"])
1465798669708 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c7 to set => self.__occ = 0 => concurrency error => new object __occ=1 set = (#1 elts, ["575e500df16b51b3f64994b8"])
1465798669712 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c6 to set => self.__occ = 0 => concurrency error => new object __occ=1 set = (#1 elts, ["575e500df16b51b3f64994b8"])
1465798669714 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c8 to set => self.__occ = 0 => concurrency error => new object __occ=1 set = (#1 elts, ["575e500df16b51b3f64994b8"])
1465798669722 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994cb to set => self.__occ = 0 => concurrency error => new object __occ=1 set = (#1 elts, ["575e500df16b51b3f64994b8"])
1465798669725 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994cd to set => self.__occ = 0 => concurrency error => new object __occ=2 set = (#2 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9"])
1465798669726 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994ca to set => self.__occ = 0 => concurrency error => new object __occ=2 set = (#2 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9"])
1465798669728 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c9 to set => self.__occ = 0 => concurrency error => new object __occ=2 set = (#2 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9"])
1465798669732 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994cc to set => self.__occ = 0 => concurrency error => new object __occ=2 set = (#2 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9"])
1465798669741 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c7 to set => self.__occ = 1 => concurrency error => new object __occ=3 set = (#3 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd"])
1465798669750 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c6 to set => self.__occ = 1 => concurrency error => new object __occ=3 set = (#3 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd"])
1465798669752 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c8 to set => self.__occ = 1 => concurrency error => new object __occ=3 set = (#3 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd"])
1465798669758 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994cb to set => self.__occ = 1 => concurrency error => new object __occ=3 set = (#3 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd"])
1465798669764 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994ca to set => self.__occ = 2 => concurrency error => new object __occ=4 set = (#4 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd","575e500df16b51b3f64994c7"])
1465798669772 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c9 to set => self.__occ = 2 => concurrency error => new object __occ=4 set = (#4 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd","575e500df16b51b3f64994c7"])
1465798669778 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994cc to set => self.__occ = 2 => concurrency error => new object __occ=3 set = (#3 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd"])
1465798669792 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c8 to set => self.__occ = 3 => concurrency error => new object __occ=4 set = (#4 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd","575e500df16b51b3f64994cc"])
1465798669794 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c6 to set => self.__occ = 3 => concurrency error => new object __occ=4 set = (#4 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd","575e500df16b51b3f64994cc"])
1465798669798 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994cb to set => self.__occ = 3 => concurrency error => new object __occ=4 set = (#4 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd","575e500df16b51b3f64994cc"])
1465798669815 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994ca to set => self.__occ = 4 => concurrency error => new object __occ=5 set = (#5 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd","575e500df16b51b3f64994cc","575e500df16b51b3f64994c6"])
1465798669825 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c9 to set => self.__occ = 4 => concurrency error => new object __occ=5 set = (#5 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd","575e500df16b51b3f64994cc","575e500df16b51b3f64994c6"])
1465798669838 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994cb to set => self.__occ = 4 => concurrency error => new object __occ=3 set = (#3 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd"])
1465798669864 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994ca to set => self.__occ = 5 => concurrency error => new object __occ=3 set = (#3 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd"])
1465798669869 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c9 to set => self.__occ = 5 => concurrency error => new object __occ=3 set = (#3 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd"])
1465798669940 >>> BGCHECK #2.2 OCC >>> Trying to add 575e500df16b51b3f64994c9 to set => self.__occ = 3 => concurrency error => new object __occ=4 set = (#4 elts, ["575e500df16b51b3f64994b8","575e500df16b51b3f64994b9","575e500df16b51b3f64994cd","575e500df16b51b3f64994ca"])
PS:如果有用,我可以分享更详细的日志或任何内容
我提到的各个部分的代码
测试脚本
const when = require('when');
const request = require('request-promise');
let promiseArray = [];
for (let i = 0; i < 10; i++) {
promiseArray.push($createComment("Test comment #"+i));
}
return when.all(promiseArray).then(function(array) {
// do something with array
}
function $createComment(text) {
let options = {
method: "POST",
uri: "<uri>",
json: true
};
return request(optionsToMyRoute);
}
后端代码
app.post("<uri>", function(req, res, next() {
// do create Comment
const commentId = // id of the comment I just created; no issue here so I'm skipping that code
const creator = // User class that adds a few methods including addToSet & dbAddToSet (see below)
creator.addToSet("userData.comments", commentId);
});
用户类
// User objects have the same properties as the underlying's Collection (_id, __v, userData.comments, ...)
User.prototype.addToSet = function(property, value) {
const self = this;
function $addToSet(dbObj, retries) {
if (retries === undefined) retries = 100;
return dbObj.dbAddToSet(property, value, options).then(
function(valuesAddedList) {
return when.resolve(valuesAddedList);
}, function(err) {
if (err instanceof ConcurrencyDBError) { // Own Error class to identify & handle conflicts
let $refreshedObject = self._convert(err.object); // convert a JSON into a proper User object
const $dbObjProp = u.getProperty(dbObj, property);
const $refreshedObjProp = u.getProperty($refreshedObject, property);
debug("::ADDTOSET::$ADDTOSET::LOG Concurrency error trying to add %s to {__v: %d, set: #%s elts} => retrying with refreshed object {__v: %d, set: #%d elts} (retries=%d)", value, dbObj.__v, $dbObjProp ? $dbObjProp.length : "na", $refreshedObject.__v, $refreshedObjProp ? $refreshedObjProp.length : "na", retries);
if (retries > 0) {
return $addToSet($refreshedObject, retries-1);
} else {
err.message += " => max number of retries reached, failing";
}
}
// else
const message = err.message || "::ADDTOSET::$ADDTOSET::ERROR Unexpected error";
return when.reject(GenericError.create(message, err)); // GenericError is a simple Error wrapper
}
);
}
return $addToSet(self);
}
User.prototype.dbAddToSet = function(property, value, options) {
var self = this;
// Use __occ to ensure we have the proper version of the object
const $id = new ObjectID(self._id);
const query = {
'_id': $id,
'__occ': self.__occ
};
const update = {
'$addToSet': {},
'$inc': {'__occ': 1}
};
let defer = when.defer();
console.log("\t%s >>> BGCHECK #1 >>> Trying to add %s to set => self.__occ = %d, self.set = (#%s elts, %j) (query=%j, update=%j)", Date.now(), value, self.__occ, $property ? $property.length: "na", $property, query, update);
self.$getMongoCollection().updateOne(query, update, { w: "majority", j: true }, function(err, result) {
if (err) return defer.reject(err);
const updateResult = result.result; // Expect a {ok, n, nModified}
if ((updateResult.ok == 1) && (updateResult.n == 0)) {
console.log("\t%s >>> BGCHECK #2.1 OCC >>> Trying to add %s to set => self.__occ = %d => concurrency error, loading new object", Date.now(), value, self.__occ);
self.$getMongoCollection().find({_id: $id}).toArray(function(err, results) {
if (err) return when.reject(err);
const obj = results[0];
// >>> debug
let $objProp = u.getProperty(obj, property);
console.log("\t%s >>> BGCHECK #2.2 OCC >>> Trying to add %s to set => self.__occ = %d => concurrency error => new object __occ=%d set = (#%s elts, %j)", Date.now(), value, self.__occ, obj.__occ, $objProp ? $objProp.length : "na", $objProp);
// <<< debug
return defer.reject(new ConcurrencyDBError(util.format("Concurrency error trying to update Object with __occ == %d", self.__occ), obj));
});
} else {
console.log("\t%s >>> BGCHECK #3 OK >>> Trying to add %s to set => self.__occ = %d => successfull, self.set = (#%s elts, %j) (updateResult=%j)", Date.now(), value, self.__occ, $property ? $property.length: "na", $property, updateResult);
return defer.resolve(true);
}
});
return defer.promise;
}
function $getMongoCollection() {
// returns a properly initialized Mongo collection
}