如何防止MongoDB由于异步而创建多个记录

时间:2015-09-02 06:19:58

标签: node.js mongodb asynchronous mongoose mongodb-query

我的应用程序变得有点噩梦,这是我在其他地方无法找到的非标准MongoDB问题。

我的服务器流程如下:

  1. 用户将包含{names, emails and company domains}的对象列表上传到我的服务器
  2. 我的服务器将所有这些转换为Person个对象。
  3. 保存Person后,我会搜索MongoDB以查看此人Domain的记录是否存在。
  4. 如果存在,我会将Person的Mongo _id添加到Domain的用户列表中。
  5. 如果它不存在,我会创建一个新的Domain文档并保存。
  6. 这在理论上有效,但是,由于Async,有时我会立即向Domain保护程序发送数千个Person个对象。这意味着(至少,我认为发生了什么):

    • Mongo搜索"域1",看到没有文档所以创建一个,然后保存一个。
    • 虽然这种情况仍在发生,但Mongo会搜索" Domain 1"来自一个单独的用户。目前还没有保存任何文件,所以它找不到任何文件并创建一个新文件。
    • 现在我有两个具有相同域名标识的文档。

    以下是我目前正在使用的代码:

    Domain.findOne({
      domain: domn
    }, function(err, rec) {
      if (err) {
        console.log("Domain finding error: " + err)
        bigCount.doneDoms++;
        checkCount()
      } else if (rec){
        var tempObj = {}
            tempObj['$addToSet'] = { users: id }
            tempObj['$addToSet'].emails = user.email;
    
        if (userDoms.indexOf(rec._id) === -1) {
          userDoms.push(rec._id)
        }
    
        Domain.update({domain: domn}, tempObj, function(err) {
          if (err) {
            console.log("Old rec save Error: " + err)
            bigCount.doneDoms++;
            checkCount();
          }else{
             // Saved Document
          }
        });
    
      } else { 
        var newDom = new Domain();
        newDom.domain = user.domain;
        newDom.company = user.company;
        newDom.users = [];
        newDom.users.push(id);
        newDom.emails = [];
        newDom.emails.push(user.email);
    
        newDom.save(function(err, record) {
          if (err) {
            console.log("Dom save error: " + err)
          } else {
            // Saved Document
          }
        });
    
      }
    })
    

    也许问题的精简版本是,如何处理这样的事情:

    var arr = [{dom: 'dom1.com', user: 'James'}, {dom: 'dom1.com', user: "Phil"}, {dom: 'dom1.com', user: "Jess"} ...x1000... {dom: 'dom1.com', user: "Chris"];
    
    for(var i - 0; i< arr.length; i++){
      var dom = arr[i]; var user = arr[i].user;
      Domain.findOne({domain: dom}, function(err, rec){
        if(rec){
          // Update old rec
          if(rec.users.indexOf(user) === -1){
             rec.users.push(user);
          }
          rec.save();
        ]else{
    
         // Make a new rec
         var rec = New Domain();
         rec.users = [user]
         rec.save();
        }
      })
    }
    

    由于速度/异步,这里会创建很多记录,当我真的只想要一个

2 个答案:

答案 0 :(得分:2)

我个人会继续采用这种做法,你会采用错误的方法,以及你可以在这里使用一些流量控制。

无论&#34;列表&#34;来源,应该发生的一般流程是:

  • 为用户实例化对象(毕竟你获得了_id

  • 查找域数据(如果存在),如果不存在,则在同时添加用户时创建域数据。 (非常可能)

  • 最后将匹配的域添加到用户并保存

这一切都遵循了.findOneAndUpdate()以及&#34; upsert&#34;选项,如果找不到,将创建一个新文档,并且无论如何都会返回找到或创建的结果文档。

所以对于一些node async库助手,这里是:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

var userSchema = new Schema({
  name: { type: String, required: true },
  domain: { type: Schema.Types.ObjectId, ref: 'Domain' }
});

var domainSchema = new Schema({
  name: { type: String, required: true },
  users: [{ type: Schema.Types.ObjectId, ref: 'User' }]
});

var User = mongoose.model('User',userSchema),
    Domain = mongoose.model('Domain',domainSchema);

mongoose.connect('mongodb://localhost/domains');

var arr = [
  {dom: 'dom1.com', user: "James"},
  {dom: 'dom2.com', user: "Phil"},
  {dom: 'dom1.com', user: "Jess"},
  {dom: 'dom1.com', user: "Chris"},
  {dom: 'dom3.com', user: "Jesse"}
];

async.series(
  [
    // Clean removal of data for demo
    function(callback) {
      async.each([User,Domain],function(model,callback) {
        model.remove({},callback);
      },callback);
    },

    // The actual insertion process
    function(callback) {
      async.eachLimit(arr,10,function(item,callback) {
        var user = new User({ name: item.user });

        // user already has the _id

        Domain.findOneAndUpdate(
          { "name": item.dom },
          { "$push": { "users": user._id } },
          { "new": true, "upsert": true },
          function(err,domain) {
            if (err) callback(err);
            user.domain = domain._id;   // always returns something

            // now save the user
            user.save(callback);
          }
        );

      },callback);
    },

    // List back populated as the proof
    function(callback) {
      User.find({}).populate('domain').exec(function(err,users) {
        if (err) callback(err);

        //console.log(users);
        //callback();

        var options = {
          path: 'domain.users',
          model: 'User'
        };

        User.populate(users,options,function(err,results) {
          if (err) callback(err);
          console.log( JSON.stringify( results, undefined, 2 ) );
          callback();
        });
      });
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

这将产生如下输出:

[
  {
    "_id": "55e6aa0e85e8b9102179f5c2",
    "domain": {
      "_id": "55e6aa0ecb536c5a93574ff5",
      "name": "dom1.com",
      "__v": 0,
      "users": [
        {
          "_id": "55e6aa0e85e8b9102179f5c2",
          "domain": "55e6aa0ecb536c5a93574ff5",
          "name": "James",
          "__v": 0
        },
        {
          "_id": "55e6aa0e85e8b9102179f5c4",
          "domain": "55e6aa0ecb536c5a93574ff5",
          "name": "Jess",
          "__v": 0
        },
        {
          "_id": "55e6aa0e85e8b9102179f5c5",
          "domain": "55e6aa0ecb536c5a93574ff5",
          "name": "Chris",
          "__v": 0
        }
      ]
    },
    "name": "James",
    "__v": 0
  },
  {
    "_id": "55e6aa0e85e8b9102179f5c3",
    "domain": {
      "_id": "55e6aa0ecb536c5a93574ff6",
      "name": "dom2.com",
      "__v": 0,
      "users": [
        {
          "_id": "55e6aa0e85e8b9102179f5c3",
          "domain": "55e6aa0ecb536c5a93574ff6",
          "name": "Phil",
          "__v": 0
        }
      ]
    },
    "name": "Phil",
    "__v": 0
  },
  {
    "_id": "55e6aa0e85e8b9102179f5c4",
    "domain": {
      "_id": "55e6aa0ecb536c5a93574ff5",
      "name": "dom1.com",
      "__v": 0,
      "users": [
        {
          "_id": "55e6aa0e85e8b9102179f5c2",
          "domain": "55e6aa0ecb536c5a93574ff5",
          "name": "James",
          "__v": 0
        },
        {
          "_id": "55e6aa0e85e8b9102179f5c4",
          "domain": "55e6aa0ecb536c5a93574ff5",
          "name": "Jess",
          "__v": 0
        },
        {
          "_id": "55e6aa0e85e8b9102179f5c5",
          "domain": "55e6aa0ecb536c5a93574ff5",
          "name": "Chris",
          "__v": 0
        }
      ]
    },
    "name": "Jess",
    "__v": 0
  },
  {
    "_id": "55e6aa0e85e8b9102179f5c5",
    "domain": {
      "_id": "55e6aa0ecb536c5a93574ff5",
      "name": "dom1.com",
      "__v": 0,
      "users": [
        {
          "_id": "55e6aa0e85e8b9102179f5c2",
          "domain": "55e6aa0ecb536c5a93574ff5",
          "name": "James",
          "__v": 0
        },
        {
          "_id": "55e6aa0e85e8b9102179f5c4",
          "domain": "55e6aa0ecb536c5a93574ff5",
          "name": "Jess",
          "__v": 0
        },
        {
          "_id": "55e6aa0e85e8b9102179f5c5",
          "domain": "55e6aa0ecb536c5a93574ff5",
          "name": "Chris",
          "__v": 0
        }
      ]
    },
    "name": "Chris",
    "__v": 0
  },
  {
    "_id": "55e6aa0e85e8b9102179f5c6",
    "domain": {
      "_id": "55e6aa0ecb536c5a93574ff7",
      "name": "dom3.com",
      "__v": 0,
      "users": [
        {
          "_id": "55e6aa0e85e8b9102179f5c6",
          "domain": "55e6aa0ecb536c5a93574ff7",
          "name": "Jesse",
          "__v": 0
        }
      ]
    },
    "name": "Jesse",
    "__v": 0
  }
]

因此,所有域都已创建或在存在时重新使用,我们使用$push同时将用户添加到列表中,因为我们已经拥有_id用户创建实例后。

在返回域名文档时,无论是新文档还是找到的文档,您只需在用户上设置域并保存即可。

async.eachLimit也是一个特殊的&#34; &#34;限制&#34;循环下运行的并发进程数。这是真实场景中的明智做法,因为您不希望同时发生每次更新。

此外,无论过程如何,&#34; Domain&#34;不可能不止一次创建。 MongoDB的原子操作将阻止这种情况,您只能获得现有的返回或新文档,具体取决于请求时的内容。

正如您在输出中所看到的,所有内容都可以很好地填充,以便&#34;用户&#34;和&#34;域&#34;细节在各个层面都可见。

故事的道德是&#34;不要把自己束缚在坚持一件事并且一次又一次地改变&#34; 的结。只做一次,完成它。它肯定更快。

答案 1 :(得分:1)

§addToSetasync一起完成了您正在寻找的内容:

var items = [
    {dom: 'dom1.com', user: "Johnny"}, 
    {dom: 'dom1.com', user: "Doggie"}, 
    {dom: 'dom1.com', user: "Lisa"},
    {dom: 'dom2.com', user: "Mark"}, 
    {dom: 'dom3.com', user: "Denny"}
];

async.each(items, function(item, callback){
        Domain.findOneAndUpdate(
            {domain: item.dom}, 
            {$addToSet: {users: item.user}},
            { "new": true, "upsert": true }, 
            callback
        );
    }, 
    function (err){
        // done / handle errors
    }
);

输出:

[{
    "_id": ObjectID("55e6c9bf63006d730254ea8b"),
    "domain": "dom1.com",
    "users": [
        "Johnny",
        "Doggie",
        "Lisa"
    ]
},
{
    "_id": ObjectID("55e6c9bf63006d730254ea8c"),
    "domain": "dom2.com",
    "users": [
        "Mark"
    ]
},
{
    "_id": ObjectID("55e6c9bf63006d730254ea8d"),
    "domain": "dom3.com",
    "users": [
        "Denny"
    ]
}]