【问题标题】:Find largest document size in MongoDB在 MongoDB 中查找最大的文档大小
【发布时间】:2013-06-01 22:35:46
【问题描述】:

是否可以在 MongoDB 中找到最大的文档大小?

db.collection.stats() 显示平均大小,这并不具有代表性,因为在我的情况下,大小可能会有很大差异。

【问题讨论】:

标签: mongodb


【解决方案1】:

正如Xavier Guihot 已经提到的,Mongo 4.4 中引入了一个新的$bsonSize 聚合运算符,它可以为您提供对象的大小(以字节为单位)。除此之外只是想提供我自己的例子和一些统计数据。

使用示例:

// I had an `orders` collection in the following format
[
  {
    "uuid": "64178854-8c0f-4791-9e9f-8d6767849bda",
    "status": "new",
    ...
  },
  {
    "uuid": "5145d7f1-e54c-44d9-8c10-ca3ce6f472d6",
    "status": "complete",
    ...
  },
  ...
];

// and I've run the following query to get documents' size
db.getCollection("orders").aggregate(
  [
    {
      $match: { status: "complete" } // pre-filtered only completed orders
    },
    {
      $project: {
        uuid: 1,
        size: { $bsonSize: "$$ROOT" } // added object size
      }
    },
    {
      $sort: { size: -1 }
    },
  ],
  { allowDiskUse: true } // required as I had huge amount of data
);

因此,我收到了按大小降序排列的文档列表。

统计:

对于约 3M 条记录和约 70GB 大小的集合,上述查询耗时约 6.5 分钟。

【讨论】:

    【解决方案2】:

    Mongo 4.4 开始,新的聚合运算符 $bsonSize 在编码为 BSON 时返回给定文档的大小(以字节为单位)。

    因此,为了找到大小最大的文档的bson大小:

    // { "_id" : ObjectId("5e6abb2893c609b43d95a985"), "a" : 1, "b" : "hello" }
    // { "_id" : ObjectId("5e6abb2893c609b43d95a986"), "c" : 1000, "a" : "world" }
    // { "_id" : ObjectId("5e6abb2893c609b43d95a987"), "d" : 2 }
    db.collection.aggregate([
      { $group: {
        _id: null,
        max: { $max: { $bsonSize: "$$ROOT" } }
      }}
    ])
    // { "_id" : null, "max" : 46 }
    

    这个:

    • $groups 所有项目一起
    • $projects 文件的$max$bsonSize
    • $$ROOT 代表我们获得 bsonsize 的当前文档

    【讨论】:

    • 这个解决方案对我来说很好 - 谢谢!
    【解决方案3】:

    嗯..这是一个老问题..但是 - 我想分享我的一分钱

    我的做法——使用 Mongo mapReduce 函数

    首先 - 让我们获取每个文档的大小

    db.myColection.mapReduce
    (
       function() { emit(this._id, Object.bsonsize(this)) }, // map the result to be an id / size pair for each document
       function(key, val) { return val }, // val = document size value (single value for each document)
       { 
           query: {}, // query all documents
           out: { inline: 1 } // just return result (don't create a new collection for it)
       } 
    )
    

    这将返回所有文档大小,但值得一提的是,将其保存为集合是一种更好的方法(结果是 result 字段内的结果数组)

    第二 - 让我们通过操作这个查询来获得文档的最大大小

    db.metadata.mapReduce
    (
        function() { emit(0, Object.bsonsize(this))}, // mapping a fake id (0) and use the document size as value
        function(key, vals) { return Math.max.apply(Math, vals) }, // use Math.max function to get max value from vals (each val = document size)
        { query: {}, out: { inline: 1 } } // same as first example
    )
    

    这将为您提供一个值等于最大文档大小的单个结果

    简而言之:

    您可能希望使用第一个示例并将其输出保存为集合(将 out 选项更改为您想要的集合名称)并对其应用进一步的聚合(最大大小、最小大小等)

    -或-

    您可能希望使用单个查询(第二个选项)来获取单个统计信息(最小值、最大值、平均值等)

    【讨论】:

    • 您的第二个示例非常适合小型数据集,但实际上在包含大约 400k 文档的集合上运行时,我的(托管)服务器因内存不足错误而关闭。是什么消耗了所有的内存?它真的不能处理生成一个包含 400k 元素的数组来传递给 reduce 函数吗?文档说它应该处理多达一半的 Mongo 的 16MB 限制作为发出的值参数和作为减少的输入。每次发射应该只返回 8 个字节!如果接近内存限制,它不应该多次调用 reduce 吗?这是怎么回事?
    • 这是一个重要的基准 - 感谢您与我们分享这些数据!对于您的问题:我认为这取决于两个因素 - 首先:托管机器上的可用资源,其次 - 当您执行查询时这台机器处理的负载量(这是 Mongo 处理的唯一想法吗?)。然而,这种行为远没有被接受——你不应该通过执行这样的活动来“关闭”你的服务器。我觉得这很令人不安。就个人而言,我认为这个评论应该变成一个问题(可能在serverfault.com
    【解决方案4】:

    灵感来自Elad Nana's package,但可以在 MongoDB 控制台中使用:

    function biggest(collection, limit=100, sort_delta=100) {
      var documents = [];
      cursor = collection.find().readPref("nearest");
      while (cursor.hasNext()) {
        var doc = cursor.next();
        var size = Object.bsonsize(doc);
        if (documents.length < limit || size > documents[limit-1].size) {
          documents.push({ id: doc._id.toString(), size: size });
        }
        if (documents.length > (limit + sort_delta) || !cursor.hasNext()) {
          documents.sort(function (first, second) {
            return second.size - first.size;
          });
          documents = documents.slice(0, limit);
        }
      }
      return documents;
    }; biggest(db.collection)
    
    • 使用光标
    • 提供limit 最大的文档列表,而不仅仅是最大的文档
    • 排序和剪切输出列表到limit每个sort_delta
    • nearest 用作read preference(如果您在从节点上,您可能还希望在连接上使用rs.slaveOk() 以便能够列出集合)

    【讨论】:

      【解决方案5】:

      使用aggregation framework 和对集合中文档的一点了解,在 MongoDB 集合中查找最大文档的速度可能比其他答案快约 100 倍。此外,您将在几秒钟内获得结果,而使用其他方法(forEach,或更糟糕的是,将所有文档发送给客户端)只需几分钟。

      您需要知道文档中的哪些字段可能是最大的字段 - 您几乎总是会知道的。只有两个实用的1 MongoDB types 可以具有可变大小:

      • 数组
      • 字符串

      聚合框架可以计算每个的长度。请注意,您不会获得数组的字节大小,而是元素的长度。但是,更重要的是异常文档是什么,而不是它们占用了多少字节。

      这是对数组的处理方式。例如,假设我们在社交网络中有一组用户,并且我们怀疑数组 friends.ids 可能非常大(实际上,您可能应该保持一个单独的字段,如 friendsCount 与数组同步,但是对于例如,我们假设它不可用):

      db.users.aggregate([
          { $match: {
              'friends.ids': { $exists: true }
          }},
          { $project: { 
              sizeLargestField: { $size: '$friends.ids' } 
          }},
          { $sort: {
              sizeLargestField: -1
          }},
      ])
      

      关键是使用$size aggregation pipeline operator。它只适用于数组,那么文本字段呢?我们可以使用$strLenBytes operator。假设我们怀疑bio 字段也可能非常大:

      db.users.aggregate([
          { $match: {
              bio: { $exists: true }
          }},
          { $project: { 
              sizeLargestField: { $strLenBytes: '$bio' } 
          }},
          { $sort: {
              sizeLargestField: -1
          }},
      ])
      

      您还可以使用$sum 组合$size$strLenBytes 来计算多个字段的大小。在绝大多数情况下,20% of the fields will take up 80% of the size(如果不是 10/90 甚至 1/99),并且大字段必须是字符串或数组。


      1 从技术上讲,很少使用的binData 类型也可以具有可变大小。

      【讨论】:

      • @Sammaye 接受的答案中的代码由客户端外壳执行,而不是由数据库服务器执行。因此,它使用游标将每个文档一个一个地加载到客户端,计算它的大小,并确定最大的一个。是的,它不会将完整的集合存储在客户端的内存中,但它仍然需要通过网络传输数据。因此,这种方法对于大型集合不太有用。
      • @Sammaye 我不确定聚合如何在分片之间传输数据,但在单服务器配置中,如果您可以避免传输完整集合并计算服务器上的所有内容,这是一个好点。即使 db 将所有数据加载到它的内存中。
      • 需要注意的一点是,大部分加速可能来自客户端/服务器往返,其中客户端是您的笔记本电脑,服务器是跨 WAN 的远程数据库主机。因此,在 在 mongodb 主机上(即您连接到本地主机上的数据库)运行其他客户端脚本化 vs agg 框架解决方案时,它必须有多快会很有趣(我怀疑可以忽略不计)...
      【解决方案6】:

      你可以使用一个小的 shell 脚本来获取这个值。

      注意:这将执行全表扫描,这在大型集合上会很慢。

      let max = 0, id = null;
      db.test.find().forEach(doc => {
          const size = Object.bsonsize(doc); 
          if(size > max) {
              max = size;
              id = doc._id;
          } 
      });
      print(id, max);
      

      【讨论】:

      • 我假设这个大小以字节为单位?
      • @akki,是的,bsonsize 返回字节值(根据mognodb docs
      • 有没有办法不将每个文档都加载到客户端来计算其大小?也许以某种方式使用聚合。
      • @BlackOverlord:yes。该解决方案比这个解决方案快得多。
      • 工作!很慢,但是工作!!!
      【解决方案7】:

      如果您正在处理一个庞大的集合,那么一次将其全部加载到内存中是行不通的,因为您需要的 RAM 比整个集合的大小还要多。

      相反,您可以使用我创建的以下包批量处理整个集合: https://www.npmjs.com/package/mongodb-largest-documents

      您所要做的就是提供 MongoDB 连接字符串和集合名称。脚本完成批量遍历整个集合后,将输出前 X 个最大的文档。

      【讨论】:

      • 这正是内置游标所允许的。它将数据流式传输,而不是将整个集合存储到 ram。
      • 嗨@dmo,你能提供一个命令来通过内置光标实现这个吗?
      • collection.find() 返回一个游标。游标是数据流。所以在 JS 中你可以做这样的事情...jsfiddle.net/ro6efkdz
      • @dmo:cursor.on('data', ...) 方法与the accepted answer 相比如何?是不是更快了?它消耗的内存是否更少?
      • 这个答案没有多大意义,任何客户端驱动程序的默认光标都不会将集合加载到内存中,事实上,如果您要按照 Dan 提到的聚合框架路线走下去,那么它会将整个结果集加载到内存中。值得注意的是,它与接受的答案完全相同,仅在 node.js github.com/eladnava/mongodb-largest-documents/blob/master/lib/…
      【解决方案8】:

      注意:这将尝试将整个结果集存储在内存中(来自.toArray)。小心大数据集。不要在生产中使用! Abishek 的答案具有在游标上工作而不是在内存数组中工作的优势。

      如果你还想要 _id,试试这个。给定一个名为“请求”的集合:

      // Creates a sorted list, then takes the max
      db.requests.find().toArray().map(function(request) { return {size:Object.bsonsize(request), _id:request._id}; }).sort(function(a, b) { return a.size-b.size; }).pop();
      
      // { "size" : 3333, "_id" : "someUniqueIdHere" }
      

      【讨论】:

      • 运行接受的答案后,这是任何人都想运行的下一个脚本!
      • 运行此程序时出现错误:错误:断言 src\mongo\util\net\message_port.cpp:195 src/mongo/shell/query.js:113
      • 这应该是公认的答案。对大型集合调用 toArray() 可能会使客户端崩溃。您不能将 10 TB 的数据拉入客户端的内存,然后尝试map 它。您需要对其进行迭代并让驱动程序处理批处理。
      • @PeteGarafano 它在答案中说得很清楚,它将把它全部拉入内存并且不用于生产。不要因为您复制并粘贴到产品中而对我投反对票。
      • 这是一个使用using aggregation 的更快的解决方案,它也不需要将整个结果集带到客户端。
      猜你喜欢
      • 1970-01-01
      • 2015-05-03
      • 1970-01-01
      • 2020-11-18
      • 2013-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-21
      相关资源
      最近更新 更多