MongoDB:如果使用$addToSet或$push,您是否应该预先分配文档?

3
我是一名有帮助的助手,以下是您需要翻译的内容:

我一直在学习MongoDB,了解到强烈建议在插入文档时完全构建文档结构(预分配),这样对该文档未来的更改就不需要将文档移动到磁盘上。在使用$addToSet或$push时是否适用相同规则?

例如,假设我有以下文档:

"_id" : "rsMH4GxtduZZfxQrC",
"createdAt" : ISODate("2015-03-01T12:08:23.007Z"),
"market" : "LTC_CNY",
"type" : "recentTrades",
"data" : [ 
    {
        "date" : "1422168530",
        "price" : 13.8,
        "amount" : 0.203,
        "tid" : "2435402",
        "type" : "buy"
    }, 
    {
        "date" : "1422168529",
        "price" : 13.8,
        "amount" : 0.594,
        "tid" : "2435401",
        "type" : "buy"
    }, 
    {
        "date" : "1422168529",
        "price" : 13.79,
        "amount" : 0.594,
        "tid" : "2435400",
        "type" : "buy"
    }
]

我正在使用以下命令之一向data字段添加新的对象数组(newData):

$addToSet用于在数组末尾添加:

Collection.update(
  { _id: 'rsMH4GxtduZZfxQrC' },
  {
    $addToSet: {
      data: {
        $each: newData
      }
    }
  }
);

使用$push(带有$position参数)将元素添加到数组的前面:

Collection.update(
  { _id: 'rsMH4GxtduZZfxQrC' },
  {
    $push: {
      data: {
        $each: newData,
        $position: 0
      }
    }
  }
);

文档中的data数组会因为从newData添加的新对象而增长。那么这种类型的文档更新是否会导致文档在磁盘上移动?
对于这个特定的系统,这些文档中的data数组可以增长到75k个对象以上。如果每次$addToSet或$push更新后这些文档确实被移动到磁盘上,那么应该在插入时定义带有75k个null值的文档(data: [null,null...null]),然后随着时间的推移使用$set替换值吗?谢谢!
3个回答

4
我了解建议完全构建(预先分配)文档结构,在插入点完成,这样将来对该文档的更改不需要在磁盘上移动文档。在使用$addToSet或$push时是否适用?
如果可行,这是推荐的,但通常不适用。时间序列数据是一个显著的例外。它不太适用于$addToSet和$push,因为它们倾向于通过增长数组来增加文档的大小。
停止。您确定要使用数万个条目不断增长的数组吗?您是否要查询特定的条目?您是否要索引数组条目中的任何字段?您可能需要重新考虑文档结构。也许您希望每个数据条目都是一个单独的文档,其中包含像市场、类型、创建时间等字段的副本?您不必担心文档移动。
为什么数组会增长到75K个条目?您能减少每个文档的条目吗?这是时间序列数据吗?预分配文档并使用mmap存储引擎进行就地更新非常好,但并不适用于每种用例,并且这不是MongoDB表现良好的要求。
“应该在插入时使用75k个null(data:[null,null ... null])来定义文档,然后也许使用$set随着时间的推移替换值?”
不,这并不是真正有帮助的。文档大小将基于数组中null值的BSON大小计算,因此当您用另一种类型替换null时,大小将增加,并且您将获得文档重写。您需要使用所有字段设置为其类型的默认值的对象预分配数组,例如:
{
    "date" : ISODate("1970-01-01T00:00:00Z")    // use a date type instead of a string date
    "price" : 0,
    "amount" : 0,
    "tid" : "000000", // assuming 7 character code - strings icky for default preallocation
    "type" : "none"    // assuming it's "buy" or "sell", want a default as long as longest real values
}

1
谢谢您的回复,这非常有帮助!是的,这是时间序列数据。我正在基于新对象生成几个课程分辨率(大约每秒1个对象),并且这些文档是用于客户端订阅的文档。但是,我正在尝试找出存储原始对象的最佳方法,我基本上只想保留它们以备将来参考,例如由于系统故障需要重新生成我的课程分辨率等。什么是存储数十万这些大小的原始对象的最佳方法,而客户端不需要它们? - Jon Cursi
1
目前,我在原帖中描述了文档结构。其中有一个data字段,它是一个不断增长的数组。一旦该数组增长到75k个对象,我就会插入一个具有相同结构的新文档,并开始进行添加操作。因此,我积累了许多长度为75k对象的文档。之所以选择75k是因为由于这种特定的对象大小,75k个对象等于大约7.5MB的文档大小,我不想接近16MB的硬限制,以避免控制台警告。也许您还可以告诉我这种方法是否正确? - Jon Cursi

3
MongoDB使用二次方分配策略来存储文档,这意味着它将为存储分配文档大小的平方。因此,如果您的嵌套数组不会导致总增长超过原始大小的平方,则Mongo不需要重新分配文档。
参见:http://docs.mongodb.org/manual/core/storage/

1
2 的幂分配并不一定能消除数组超出这些长度的问题。最佳情况是根据实际存储需求来评估您的使用模式并进行设计/预分配。 - Neil Lunn

3
底线是,任何“文档增长”几乎总是会导致存储分配的“物理移动”,除非您通过某种方式在原始文档提交时进行了“预分配”。是的,“2的幂次方”分配存在,但这并不总是对您的存储情况有效。
另一个“陷阱”在于"封顶集合",在那里确实存在“隐藏陷阱”,即如果这些指令超出了“oplog”期间,副本集成员可能不会将这样的“预分配”方法“复制”到其他成员中。
任何结构的增长都将导致该文档在其超出最初提供空间的空间时被“移动”。
为了确保这种情况不会发生,您应始终根据原始创建时的预期数据量进行“预分配”。但已经描述过明显的警告条件。

你会推荐在插入时使用75k空值(data: [null,null...null])来定义文档,然后也许使用$set随着时间的推移替换空值为实际对象,而不是使用$addToSet动态增长文档吗?这样能解决问题吗?谢谢! - Jon Cursi
当您使用相同的“set”提交多个值时,服务器本身会将这些值“nullified”作为$addToSet的过程,因为它们是相同的。当然,这并不是推荐的做法,您可能应该在客户端代码中解决这个问题,然后再提交。 - Neil Lunn

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