【问题标题】:pre-allocation of records using count使用计数预分配记录
【发布时间】:2016-03-02 22:17:02
【问题描述】:

我读过记录的预分配可以提高性能,这应该是有益的,尤其是在处理时间序列数据集的许多记录时。

updateRefLog = function(_ref,year,month,day){
    var id = _ref,"|"+year+"|"+month;
    db.collection('ref_history').count({"_id":id},function(err,count){
        // pre-allocate if needed
        if(count < 1){
            db.collection('ref_history').insert({
                "_id":id
                ,"dates":[{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0},{"count":0}]
            });
        }

        // update
        var update={"$inc":inc['dates.'+day+'.count'] = 1;};
        db.collection('ref_history').update({"_id":id},update,{upsert: true},
            function(err, res){
                if(err !== null){
                    //handle error
                }
            }
        );
    });
};

我有点担心必须通过承诺可能会减慢速度,并且可能每次都检查计数会否定预分配记录的性能优势。

有没有更高效的方法来处理这个问题?

【问题讨论】:

  • 您使用的是什么版本的 MongoDB .. 如果 MongoDB 3.0 或更高版本,使用什么存储引擎?预分配对于 MMAP 存储引擎来说是一种有用的优化技术,但会增加其他不支持就地更新的存储引擎(例如 WiredTiger)的开销。

标签: javascript node.js mongodb mongodb-query pre-allocation


【解决方案1】:

“预分配”的一般陈述是关于导致文档“增长”的“更新”操作的潜在成本。如果这导致文档大小大于当前分配的空间,则文档将被“移动”到磁盘上的另一个位置以容纳新空间。这可能代价高昂,因此一般建议最初编写适合其最终“大小”的文档。

老实说,处理此类操作的最佳方法是在最初分配所有数组元素的情况下执行“更新插入”,然后仅更新所需元素的位置。这将减少到“两个”潜在写入,您可以使用 Bulk API 方法进一步减少到单个“在线”操作:

var id = _ref,"|"+year+"|"+month;
var bulk = db.collection('ref_history').initializeOrderedBulkOp();

bulk.find({ "_id": id }).upsert().updateOne({
    "$setOnInsert": {
        "dates": Array.apply(null,Array(32)).map(function(el) { return { "count": 0 }})
   }
});

var update={"$inc":inc['dates.'+day+'.count'] = 1;};
bulk.find({ "_id": id }).updateOne(update);

bulk.execute(function(err,results) {
   // results would show what was modified or not
});

或者由于较新的驱动程序更倾向于彼此保持一致,因此“批量”部分已降级为 WriteOperations 的常规数组:

var update={"$inc":inc['dates.'+day+'.count'] = 1;};

db.collection('ref_history').bulkWrite([
    { "updateOne": {
        "filter": { "_id": id },
        "update": {
            "$setOnInsert": {
                "dates": Array.apply(null,Array(32)).map(function(el) {
                    return { "count": 0 }
                })
            }
        },
        "upsert": true
    }},
    { "updateOne": {
        "filter": { "_id": id },
        "update": update
    }}
],function(err,result) {
    // same thing as above really
});

在任何一种情况下,$setOnInsert 作为唯一块只有在“upsert”实际发生时才会执行任何操作。主要情况是与服务器的唯一联系将是单个请求和响应,而不是等待网络通信的“来回”操作。

这通常是“批量”操作的用途。当您不妨向服务器发送一批请求时,它们会减少网络开销。结果显着加快了速度,除了“有序”之外,这两个操作都不真正依赖于另一个,后者是后一种情况下的默认值,由旧版.initializeOrderedBulkOp() 明确设置。

是的,“upsert”中存在“少量”开销,但与使用 .count() 进行测试并首先等待该结果相比,存在“较少”的开销。


N.B 不确定您列表中的 32 个数组条目。您可能指的是 24,但复制/粘贴占了上风。无论如何,有比硬编码更好的方法来做到这一点,正如所证明的那样。

【讨论】:

  • 我的意思是它是 31 个条目(一个月中的最大天数),但我将它切换到一个以日期作为键而不是数组的对象,这样我可以从 1 开始而不是在 0.
  • @Daniel 这也是有效的,但当然,除非您再次“预分配”空间,否则文档有可能再次移动。无论您的实际最终实现是什么,最好的办法是“更新”而不是等待计数返回。那是你的问题,因此这就是答案所说的。即使您决定只尝试完整写入一次,并且即使您之后删除了所有键/数组条目,您仍然需要决定以某种方式编写新文档。 Upserts 胜过 .find() 然后 .insert() 每次。
  • 我发现在我的测试中,最快的方法是根本不预先分配,并且 find 方法似乎比批量插入要好一些。很可能是我的设置有问题,但我将暂时推迟性能调整。
  • @Daniel 这很容易主观,不能真正测试所有案例。是的,确实“并非所有增长”都会导致文档被移动,这是发生这种情况时的主要成本。因此,如果您的文档永远不会超出初始默认分配,则无需添加所有密钥。但是它仍然没有改变您需要在某个时候创建​​一个新文档的事实,除非您确定您总是要在写入任何数据之前创建,否则最初的“upsert”仍然是要走的路。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-17
  • 2023-03-31
  • 2021-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多