【问题标题】:MongoDB workaround for document above 16mb size?16mb 以上文档的 MongoDB 解决方法?
【发布时间】:2020-10-31 06:40:22
【问题描述】:

我正在处理的 MongoDB 集合从手机获取传感器数据,并每隔 2-6 秒 ping 一次服务器。

数据量很大,4-5 小时后超过了 16mb 的限制,似乎没有任何解决方法?

我曾尝试在 Stack Overflow 上搜索它并回答了各种问题,但实际上没有人分享他们的 hack。

有没有什么办法......在数据库方面可能会像通过gridFS为大文件所做的那样分配块?

【问题讨论】:

  • 无限增长的文档是一种反模式;您可能应该重新考虑您的数据模型以更好地支持您的用例。 GridFS 方法仅适用于存储大型二进制 blob 的情况;这对您计划查询的字段的数据没有帮助(除非查询仅限于有关 GridFS 中二进制文件的元数据)。对于架构建议,您需要发布示例文档并描述您的常见更新和查询。您的 MongoDB 服务器版本和配置的存储引擎也将是相关的。

标签: mongodb


【解决方案1】:

要解决此问题,您需要对数据结构进行一些小的修改。听上去,要让您的文档超过 16mb 的限制,您必须将传感器数据嵌入到单个文档的数组中。

我不建议在这里使用 GridFS,我不认为它是最好的解决方案,这就是原因。

您可以使用一种称为分桶的技术,它实质上会将您的传感器读数分成单独的文档,从而为您解决这个问题。

它的工作方式是这样的:

假设我有一个文档,其中包含特定传感器的一些嵌入式读数,如下所示:

{
    _id : ObjectId("xxx"),
    sensor : "SensorName1",
    readings : [
        { date : ISODate("..."), reading : "xxx" },
        { date : ISODate("..."), reading : "xxx" },
        { date : ISODate("..."), reading : "xxx" }
    ]
}

上面的结构已经有一个很大的缺陷,读数数组可能会呈指数级增长,并超过 16mb 文档的限制。

所以我们可以做的是稍微改变结构,使其看起来像这样,以包含一个计数属性:

{
    _id : ObjectId("xxx"),
    sensor : "SensorName1",
    readings : [
        { date : ISODate("..."), reading : "xxx" },
        { date : ISODate("..."), reading : "xxx" },
        { date : ISODate("..."), reading : "xxx" }
    ],
    count : 3
}

这背后的想法是,当您将读数 $push 到嵌入式数组中时,每次执行的推送都会增加 ($inc) 计数变量。当你执行这个更新(推送)操作时,你会在这个“count”属性上包含一个过滤器,它可能看起来像这样:

{ count : { $lt : 500} }

然后,设置您的更新选项,以便您可以将“upsert”设置为“true”:

db.sensorReadings.update(
    { name: "SensorName1", count { $lt : 500} },
    {
        //Your update. $push your reading and $inc your count
        $push: { readings: [ReadingDocumentToPush] }, 
        $inc: { count: 1 }
    },
    { upsert: true }
)

有关 MongoDb 更新和 Upsert 选项的更多信息,请参见此处:

MongoDB update documentation

当过滤条件不满足时会发生什么(即当这个传感器没有现有文档,或者计数大于或等于 500 - 因为每次推送项目时都会增加它) ,将创建一个新文档,并且读数现在将嵌入到这个新文档中。因此,如果您正确执行此操作,您将永远不会达到 16mb 的限制。

现在,当查询数据库以获取特定传感器的读数时,您可能会返回该传感器的多个文档(而不是仅包含所有读数的一个文档),例如,如果您有 10,000 个读数,您将获得返回 20 个文档,每个文档有 500 个读数。

然后,您可以使用聚合管道和 $unwind 过滤您的读数,就好像它们是它们自己的单个文档一样。

有关 unwind 的更多信息,请参见此处,它非常有用

MongoDB Unwind

我希望这会有所帮助。

【讨论】:

  • 这是最好的方式。要了解有关处理此确切用例的分桶的更多信息,您可以访问此处:mongodb.com/blog/post/building-with-patterns-the-bucket-pattern
  • 谢谢!这实际上是我寻找的一个绝妙的解决方案。
  • @pieperu 能否也提供一个通过聚合或其他方法提取数据的示例? 16MB的限制是否也适用于聚合结果?
  • 此策略是否需要在计数字段中使用索引? (或复合索引名称+计数?)
  • 是的。任何时候读取一个字段都应该被索引覆盖。在这种情况下,它作为更新条件的一部分被读取。
【解决方案2】:

您可以在 MongoDB 中使用 GridFS 来处理此类情况。

GridFS 不是将文件存储在单个文档中,而是将文件划分为部分或块1,并将每个块存储为单独的文档。默认情况下,GridFS 使用 255 kB 的块大小;也就是说,GridFS 将一个文件分成 255 kB 的块,最后一个块除外。最后一块只有必要的大。同样,不大于块大小的文件只有一个最终块,只使用所需的空间以及一些额外的元数据。

GriFS 的文档几乎包含了实现 GridFS 所需的所有内容。你可以关注它。

由于您的数据是流式的,您可以尝试如下...

gs.write(data, callback)

其中数据是缓冲区或字符串,回调获取两个参数 - 一个错误对象(如果发生错误)和指示写入是否成功的结果值。虽然 GridStore 没有关闭,但每次写入都会附加到打开的 GridStore。

您可以关注github page 获取流媒体相关信息。

【讨论】:

  • 数据每 1-2 秒 ping 一次,所以如果我们决定缓冲它并制作一个文件,它可能会干扰从应用程序到服务器的进程和有效负载。
  • 您的数据是流式传输的吗?
  • 是的,通过套接字。
猜你喜欢
  • 2018-09-11
  • 2013-02-24
  • 2017-02-22
  • 1970-01-01
  • 1970-01-01
  • 2020-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多