【问题标题】:MongoDB: Should You Pre-Allocate a Document if Using $addToSet or $push?MongoDB:如果使用 $addToSet 或 $push,您应该预先分配文档吗?
【发布时间】:2015-03-02 11:36:39
【问题描述】:

我一直在研究 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"
    }
]

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

$addToSet 添加到数组的末尾:

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

$push (with $position) 添加到数组的前面:

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

由于从newData 添加的新对象,文档中的data 数组将增长。那么这种类型的文档更新会导致文档在磁盘上移动吗?

对于这个特定的系统,这些文档中的 data 数组可以增长到超过 75k 个对象,因此如果这些文档在每次 $addToSet 或 $push 更新后确实在磁盘上移动,那么应该定义文档插入时有 75k 空值 (data: [null,null...null]),然后可能使用 $set 随着时间的推移替换值?谢谢!

【问题讨论】:

    标签: mongodb meteor


    【解决方案1】:

    我了解强烈建议在插入时完全构建(预分配)文档结构,这样以后对该文档的更改不需要在磁盘上移动文档。这在使用 $addToSet 或 $push 时是否适用?

    如果它对用例可行,则建议使用,但通常情况并非如此。时间序列数据是一个明显的例外。它并不真正适用于$addToSet$push,因为它们倾向于通过增加数组来增加文档的大小。

    这些文档中的数据数组可以增长到超过 75k 个对象

    停下来。您确定要不断增长的包含数万个条目的数组吗?您是否要查询想要返回的特定条目?你要索引数组条目中的任何字段吗?您可能想重新考虑您的文档结构。也许您希望每个 data 条目是一个单独的文档,其中每个字段都复制了 markettypecreatedAt 等字段?您不必担心文档移动。

    为什么数组会增长到 75K 条目?您可以减少每个文档的条目吗?这是time series data?能够预先分配文档并使用 mmap 存储引擎进行就地更新是很棒的,但它并不适用于每个用例,也不是 MongoDB 性能良好的要求。

    是否应该在插入时使用 75k 空值(数据:[null,null...null])定义文档,然后也许使用 $set 来随着时间的推移替换值?

    不,这并没有真正的帮助。文档大小将根据数组中空值的 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 个对象/秒)生成几个课程分辨率,这些文档是客户端订阅中使用的文档。但是我正在尝试找出存储原始对象的最佳方法,我几乎只想保留它们,以防将来需要它们作为参考,即由于系统故障等需要重新生成我的课程分辨率。存储数十万个客户不需要的大小原始对象的最佳方式是什么?
    • 目前,我有上面原始帖子中描述的文档结构。有一个data 字段,它是一个长度增加的数组。一旦该 Array 的大小增加到 75k 个对象,我将插入一个具有相同结构的新文档并开始在那里添加ToSet。所以我积累了一堆长度为 75k 个对象的文档。选择 75k 是因为由于这个特殊的对象大小,其中 75k 相当于文档大小约为 7.5MB,我不想接近 16MB 硬限制以避免控制台警告。也许你也可以让我知道这种方法是否正确?
    【解决方案2】:

    MongoDB 使用两种分配策略的力量来存储您的文档,这意味着它将分配文档的大小^2 进行存储。因此,如果您的嵌套数组不会导致总增长大于原始大小的 2 次方,那么 mongo 将不必重新分配文档。

    见:http://docs.mongodb.org/manual/core/storage/

    【讨论】:

    • 2 的幂次分配不一定能解决数组超出这些长度的问题。最好的情况是根据实际存储需求评估您的使用模式和设计/预分配。
    【解决方案3】:

    这里的底线是任何“文档增长”几乎总是会导致存储分配的“物理移动”,除非您在原始文档提交时通过某种方式“预分配”。是的,有“二的幂”分配,但这并不总是意味着对您的存储箱有效。

    这里的额外“catch”在"capped collections",实际上“隐藏的catch”是这样的“预分配”方法可能不会被“复制”到副本集中的其他成员,如果这些指令失败在应用副本集条目的“oplog”期间之外。

    超出从“初始分配”分配的任何结构或可以应用的一般技巧导致该文档在超出其空间时在存储空间中“移动”最初提供。

    为了确保这种情况不会发生,那么您始终“预先分配”到您的原始数据上的预期规定。并且已经描述了明显的情况。

    【讨论】:

    • 您是否建议在插入时使用 75k 空值 (data: [null,null...null]) 定义文档,然后也许使用 $set 将空值随时间替换为实际对象?而不是使用 $addToSet 随着时间的推移动态增长文档?这能解决问题吗?谢谢!
    • @JonCursi 当然,当您提交具有相同“设置”结果的多个值时,这些值将被服务器本身作为$addToSet 的进程“无效”,因为它们是相同的。但不建议这样做,您可能应该在提交之前在客户端代码中对其进行排序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多