Mongoose/MongoDB,更新subdocument的数组奇慢无比
发布于 7个月前 作者 Plypy 796 次浏览

新手真诚求教。

我现在正在用Mongoose/MongoDB,为一个小网站存储用户的信息。

在做数据迁移的时候,发现保存的速度奇慢无比。存储前200个数据,只需要几秒,到后来随着数据的增多存储速度直线下降。当到600个的时候已经下降到了1个/秒,1500个的时候已经变成了2个/秒。。。这个速度实在无法接受啊。

经过测试后,我发现问题出现在了插入时与另一个Model交互的地方。是这样的,我有一个User的Model存储信息,同时还有一个Group的Model用来控制用户权限。User有一个groups的数组存储它所属于哪个,Group有一个member的subdocument的数组,存储属于他的user的ObjectId和username方便查询,简而言之就是这俩是个多对多的关系。Group有几十个,用户大概一万左右。

下面是那部分的代码,通过输出的log信息可以发现,速度就是慢在了这部分。数据量越大,这部分的运行时间越长,保存一个User的文档一直都是ms级别的。我尝试着把更改member的操作由addToSet改为了push,但是性能没有显著的提升。

User.prototype.addGroupsAndSave = function (groups, callback) {
  log.debug('before adding groups');
  // ToDo May have duplicate problems
  if (!Array.isArray(groups)) {
    groups = [groups];
  }
  var user = this;
  var userRef = {objId: user.id, username: user.username};
  async.map(groups, function addToGroup(group, cb) {
    Group.findOne({groupName: group}, function (err, group) {
      if (err) {
        return cb(err);
      }
      if (_.isEmpty(group)) {
        return cb(new Error('No such groups'));
      }
      // group.member.addToSet(userRef);
      group.member.push(userRef);
      group.save(cb);
    });
  },
  function (err) {
    if (err) {
      return callback(err);
    }
    log.debug('after adding groups');
    user.groups.addToSet.apply(user.groups,groups);
    user.save(callback);
  });
};

如果直接用mongo_native迁移的话应该会快一些,但是因为以前的数据,有一些有问题,我想用Mongoose做验证,而且这样也能测试使用环境下的性能。

问题出在哪了呢?不解决这个问题,不敢部署网站啊,性能太渣了。

7 回复

问题出在nodejs的并发上面,它是并发了,但是数据库后面的兄弟们就受苦了,人家哪处理的过来啊,我之前做统计的时候也遇到这个问题了,后来还是在nodejs这边的程序上下手搞定的

当然了,我是这个理解的,也可能不正确

是因为mongodb 文档增大导致移动了吧,预先用垃圾数据填充下试试。一般是建议不断增大的子文档弄到其它集合去

这个问题考虑过了,我在插入的使用async.eachSeries进行串行插入。

解决方案看我楼下的回复

作死的典型代表。。。。你調一下你MongoDB的ProfilingLevel看一下 你這個更新操作我覺得應該是秒級別的。。。

几番Google之后,发现问题出在了Mongoose上,对于所有的查询到的结果,其都会进行一番处理,转成MongooseDocuments。结果就是对速度会有很大的影响,相较于MongoDB_native大约是3倍的时间消耗。这个转化是为了方便再一次save时进行验证的,所以如果对大文档是只读的查询,最好在查询时设置 lean。

但是我的情况不一样,我需要更新我的member数组,不过我并不特别关心更新了后的member数组会变成什么样,而且对于member也不关心验证。所以我使用了这样的更改。

    Group.findOneAndUpdate({groupName: groupName}, {
      $addToSet: {
        member: userRef,
      }
    },{
      lean: true,
      select: 'groupName',
    }, callback);

就是说直接使用Model.findOneAndUpdate跳过Mongoose的验证,并且查询时避开member这个大数组。

是啊。。。已解决。。。

不过乃回复的时候,能不能附上原因,否则对其他人没有帮助啊

回到顶部