如何提高MongoDB的批量操作性能?

3
我有一个带有一些元数据和大量项目的对象。我过去在Mongo中存储它,并通过使用$unwind对其进行查询。然而,在极端情况下,数组变得如此之大,以至于我遇到了16MB BSON限制。
因此,我需要将数组的每个元素都存储为单独的文档。为此,我需要将元数据添加到所有元素中,以便我可以找回它们。建议我使用批量操作来完成这个任务。
然而,性能似乎非常慢。插入一个大型文档几乎是即时的,而这需要长达十秒钟的时间。
var bulk        = col.initializeOrderedBulkOp();
var metaData    = {
    hash            : hash,
    date            : timestamp,
    name            : name
};

// measure time here

for (var i = 0, l = array.length; i < l; i++) { // 6000 items
    var item = array[i];

    bulk.insert({ // Apparently, this 6000 times takes 2.9 seconds
        data        : item,
        metaData    : metaData
    });

}

bulk.execute(bulkOpts, function(err, result) { // and this takes 6.5 seconds
    // measure time here
});

批量插入 6000 个文档,总共38MB的数据(在MongoDB中转换为49MB的BSON),性能看起来非常差。 每个文档附加元数据的开销不会很大,对吗?更新两个索引的开销不会很大,对吗? 我有什么遗漏的地方吗?是否有更好的方法可以插入需要一起获取的文档组? 这不仅仅是我的笔记本电脑。服务器也是如此。这让我认为这不是配置错误,而是编程错误。 使用MongoDB 2.6.11和node适配器node-mongodb-native 2.0.49 -更新- 仅在批量添加元数据的操作中就占用了2.9秒。需要有更好的方式来解决这个问题。

2
请注意,从内部来看,驱动程序和MongoDB本身将把这个请求分解成每次1000个操作(因此实际上进行了6次发送/接收交互),并且没有单个请求可以超过16MB,所以无论如何都会对这个请求进行一些“拆分”。另外,请考虑到当调用bulk.insert()时,实际上并没有向服务器发送任何数据,而是在内存中构建数据(实际上是第二次)。直接从源代码中阅读可能更好。另外,添加相同的数据到所有内容的目的是什么? - Blakes Seven
1
可能对于您的建模目标有点宽泛,没有具体问题。这有点偏离当前问题的主题。所以您有一个包含60,000个文档的数组。您是从哪里获取它的?您能够包括读取源代码吗?因为我不确定您是否意识到在执行任何操作之前,您正在将60,000份文档存储在内存中,然后再次在内存中复制60,000份文档。如果可以直接从源代码中进行分块处理,我认为这将会获得更好的吞吐量。 - Blakes Seven
最好的做法是,你不要在评论中写“大段解释”,而是将其作为问题的编辑。我只是在解释在更新时在内存中建立两个副本的冗余基本原理。也许你应该将你想要的过程分成一系列较小的问题来解释。最终结果可能会带你走向一个与你当前设计不同但更有效的方向。 - Blakes Seven
我明白了。Q1:如何处理16MB的限制?A1:使用单独的文档。Q2:如何将元数据添加到这些文档中?A2:使用批量插入。Q3:如何提高性能?(这个问题) - Redsandro
2
谢谢你和我一起思考。然而,请假设我有充分的理由,我不是在寻求如何_不_做我即将要做的事情的建议。我想插入一个太大以至于单个文档无法容纳的数组。我只是无法相信为了执行四个查询而不是一个查询会有这么多的开销。也许你是对的,我应该用不同的方式提问。 - Redsandro
显示剩余3条评论
1个回答

1
将批量插入操作分批发送,这样可以减少对服务器的流量,从而通过将所有内容拆分为可管理的块以进行服务器提交,而不是每个语句都单独发送,从而执行高效的传输事务。采用此方法,回调中等待响应的时间也更少。
使用async模块会更好,因为即使循环输入列表也是非阻塞操作。选择批处理大小可能有所不同,但选择每1000个条目的批处理插入操作可以安全地保持在16MB BSON硬限制以下,因为整个“请求”等于一个BSON文档。
以下示例演示了如何使用async模块的whilst来迭代数组并重复调用迭代器函数,同时测试返回true。当停止或发生错误时调用回调函数。
var bulk = col.initializeOrderedBulkOp(),
    counter = 0,
    len = array.length,
    buildModel = function(index){   
        return {
            "data": array[index],
            "metaData": {
                "hash": hash,
                "date": timestamp,
                "name": name
            }
        }
    };

async.whilst(
    // Iterator condition
    function() { return counter < len },

    // Do this in the iterator
    function (callback) {
        counter++;
        var model = buildModel(counter);
        bulk.insert(model);

        if (counter % 1000 == 0) {
            bulk.execute(function(err, result) {
                bulk = col.initializeOrderedBulkOp();
                callback(err);
            });
        } else {
            callback();
        }
    },

    // When all is done
    function(err) {
        if (counter % 1000 != 0) {
            bulk.execute(function(err, result) {
                console.log("More inserts.");
            }); 
        }           
        console.log("All done now!");
    }
);

5
Mongo会自动完成这个操作。默认情况下(限制)是1000。你应该只在特定原因下执行此操作,例如如果1000个文档已经超过了16MB的限制,那么你可以手动执行。但是你需要选择小于1000的数字才有意义。 - Redsandro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接