使用mongo

时间:2016-06-13 06:01:10

标签: node.js mongodb concurrency

我尝试基于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
}

0 个答案:

没有答案