【问题标题】:MongoDB-mongoose high cpu use when saving large documents in node.js在node.js中保存大型文档时MongoDB-mongoose高cpu使用
【发布时间】:2013-09-08 02:37:19
【问题描述】:

我正在开发一个托管在 EC2 上的像素跟踪应用程序,它会在视频广告的每个请求上调用,因此它会跟踪它何时开始、完成以及是否进行了点击操作。我将 node.js 与 express 一起使用,因为我想尽可能快地响应和 mongoDB/Mongoose,因为它就像一个服务器日志结构。我几乎每毫秒都会收到请求。但是当将文档存储到集合中时,它几乎 100% 占用了大量的 CPU,最后 node.js 启动错误:

GET /pixel/impression/ad1 200 1ms
FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory

我认为是猫鼬操作在我移除部件时占用了大部分 cpu,它永远不会挂起。

在 app.js 我有:

var hostSchema = new mongoose.Schema({
  ip: String,
  date: { type: Date, default: Date.now }
});

var orderSchema = new mongoose.Schema({
  name: String,
  metricCount: {
    impression: { type: Number, default: 0 },
    clicks:  { type: Number, default: 0 },
    complete: { type: Number, default: 0 }
  },
  impressionHosts: [hostSchema],
  clicksHosts: [hostSchema],
  completeHosts: [hostSchema]
});

var Order = mongoose.model('order', orderSchema);
var Host = mongoose.model('host', hostSchema);

以及快速get方法:

app.get('/pixel/:metric/:campaignName', function(req, res){

  var campaignName = req.params.campaignName;
  var metrica = req.params.metric;

  Order.find({name: campaignName}, function(err, doc){
    newMet = {};
    newMet[metrica] = 1;

    var incomingHost = new Host({ip: req.ip});
    if(doc.length<1){
         insertNewElement(campaignName, newMet, metrica, incomingHost);
       }else {
         updateElement(doc[0], metrica, incomingHost);
         }
  });
res.end(pixel, 'binary');
});

当我评论“updateElement”函数时,node.js 执行“完美”。这里有这些函数:

function updateElement(doc, metrica, incomingHost){
    doc.metricCount[metrica]+=1;
      doc[metrica+'Hosts'].push(incomingHost);
      doc.save(function(err){
        if(err){
          console.log(err);
        }
          //console.log('Record Updated')
      });
}

function insertNewElement(campaignName, newMet, metrica, incomingHost) {
  new Order({ name : campaignName, metricCount: newMet }).save(function(err, doc){
         if (err) res.json(err);
           doc[metrica+'Hosts'].push(incomingHost);
           doc.save(function(err){
              if(err){
                console.log(err);
              }
               // console.log('new record added '+ doc.name);
            });
         });
}

我相信问题存在于推送新主机时,因为有很多问题,但由于我不是 mongoDB 专家,我不知道如何改进该方法,如果这会导致问题。由于 mongo 文档和研究,我的大部分代码都进行了调整。

如何使更新更快并避免 nodejs 上的内存错误?

谢谢!

【问题讨论】:

  • 您是否在集合中使用任何索引?由于 mongo 中缺少文档级锁定,整个数据库在写入时被锁定。您可以通过确保不需要在同一时间更新任何索引来加快此过程。
  • 不,不是真的,我相信我只是“使用” _id: 默认情况下的索引。实际上,我认为这与搜索某些内容时缺少索引有关,但我认为(就像您一样)这将无济于事。也许 doc[metrica+'Hosts'].push(...) 有问题,因为这可能是一大堆文档。你怎么看?
  • 如果您的文档不断增长,它可能会迫使它在磁盘上重新分配以防止其碎片化。我将在下面的答案中详细说明更多信息。

标签: node.js mongodb express amazon-ec2 mongoose


【解决方案1】:

当您在 mongo 中创建文档时,它会为文档分配一部分磁盘空间,并使用足够的填充来适应文档的估计增长。如果文档大小超过分配的空间,mongo 需要移动并为文档重新分配新空间。随着文档的增长,这种情况会继续发生。

为避免这种情况,您需要预先分配空间。为此,您可以将足够的数据填充到文档中,mongo 将分配足够的空间以适应最大文档大小。

在您的情况下,当您第一次插入新文档时,您将添加足够的主机子文档,以复制较大的文档。插入该文档后,您可以删除 hosts 子文档并插入正确的记录。

现在,这并不完美。 Mongo 不支持事务,因此您可能会在插入过程清理预先分配的文档之前对文档进行更新。

同样使用 mongo,请尽量缩短字段名称。每当使用时,Mongo 都会将完整的字段名称存储为文档的一部分。虽然这可能看起来不多,但它可以占大型集合中的大量磁盘空间。

【讨论】:

  • 谢谢datasage,你建议的解决方案很棒,因为在我的例子中,数据集的长度可以在一开始就计算出来。现在,对于像这样的应用程序,写入时间比每个请求(1ms/request 和 300ms 写入)需要更长的时间应该更好地切换到像 redis 这样的内存数据库?并将所有内容存储到磁盘中?
  • 您将不得不在某个地方敲击磁盘以保持持久性。将值存储在内存中和将其持久化到磁盘之间的延迟越长,发生故障时数据丢失的可能性就越大。您可以尝试的其他方法是使用 RAID 优化 IO,如果具有成本效益,请使用 SSD 实例。
  • 另一种选择是将数据写入 RDBMS 系统,然后稍后将其批处理到您的文档中。您将获得交易并且不会遇到预分配问题。 NoSQL 并不总是解决所有问题的好选择。
  • 谢谢@datasage。我想我会切换到 RDBMS,因为我没有看到任何更好的选择来更快地将新文档附加到主机列表中。
猜你喜欢
  • 2022-01-22
  • 2012-05-03
  • 1970-01-01
  • 2019-01-13
  • 2013-05-18
  • 2013-02-12
相关资源
最近更新 更多