MongoDB:文档大小是否影响查询性能?

MongoDB:文档大小是否影响查询性能?,mongodb,mongodb-query,Mongodb,Mongodb Query,假设一个手机游戏由一个MongoDB数据库支持,该数据库包含一个用户集合和数百万个文档 现在假设几十个必须与用户关联的属性-例如\u id朋友文档的值数组、它们的用户名、照片、游戏文档的\u id值数组、上次登录日期、游戏内货币计数等 我关心的是,在数百万用户文档上创建和更新大型、不断增长的数组是否会增加每个用户文档的“权重”,和/或增加整个系统的速度 我们可能永远不会对每个文档使用16mb的eclipse,但我们可以放心地说,如果我们直接存储这些不断增长的列表,我们的文档将增加10-20倍 问

假设一个手机游戏由一个MongoDB数据库支持,该数据库包含一个
用户
集合和数百万个文档

现在假设几十个必须与用户关联的属性-例如
\u id
朋友
文档的值数组、它们的用户名、照片、
游戏
文档的
\u id
值数组、上次登录日期、游戏内货币计数等

我关心的是,在数百万用户文档上创建和更新大型、不断增长的数组是否会增加每个用户文档的“权重”,和/或增加整个系统的速度

我们可能永远不会对每个文档使用16mb的eclipse,但我们可以放心地说,如果我们直接存储这些不断增长的列表,我们的文档将增加10-20倍

问题:这在MongoDB中是个问题吗?即使使用投影和索引等正确管理查询,文档大小是否也很重要?我们是否应该积极削减文档大小,例如引用外部列表与直接嵌入
\u id
值列表?

换句话说:如果我想要一个用户的
last\u login
值,那么如果我的
user
文档是100kb而不是5mb,那么只投影/选择
last\u login
字段的查询会有什么不同吗


或者:如果我想查找具有特定
上次登录值的所有用户,文档大小是否会影响此类查询?

首先,您应该花一点时间阅读MongoDB如何根据填充因子和大小分配来存储文档:

简单地说,MongoDB在存储原始文档时会尝试分配一些额外的空间,以实现增长。Power2Sizes分配成为2.6版的默认方法,它将以2的幂增加文档大小

总体而言,如果所有更新都符合原始大小分配,则性能会更好。原因是,如果他们不这样做,整个文档需要移动到其他有足够空间的地方,从而导致更多的读写操作,实际上会导致存储碎片化

如果您的文档确实要以10倍到20倍的时间增长,这可能意味着每个文档要进行多次移动,这取决于您的插入、更新和读取频率,可能会导致问题。如果是这种情况,您可以考虑以下几种方法:

1) 在初始插入时分配足够的空间,以覆盖大多数(比如90%)正常文档的生命周期增长。虽然这在开始时会降低空间使用效率,但随着文档的增长,效率会随着时间的推移而提高,而不会降低任何性能。实际上,您将提前支付存储费用,最终在以后使用这些存储以获得良好的性能

2) 创建“溢出”文档—假设一个典型的80-20规则适用,并且80%的文档将适合一定的大小。分配该金额并添加一个溢出集合,例如,如果您的文档有100多个好友或100个游戏文档,则可以指向该集合。溢出字段指向此新集合中的文档,如果溢出字段存在,则应用程序仅在新集合中查找。允许80%的用户进行正常的文档处理,并避免在80%的不需要的用户文档上浪费大量存储空间,从而增加应用程序的复杂性

无论在哪种情况下,我都会考虑通过建立适当的索引来使用覆盖查询:

覆盖查询是一种查询,其中:

all the fields in the query are part of an index, and
all the fields returned in the results are in the same index.
因为索引“覆盖”了查询,所以MongoDB可以同时匹配查询 条件并仅使用索引返回结果;MongoDB有 不需要查看文档,只需查看索引即可完成 询问

仅查询索引可能比查询文档快得多 在索引之外。索引键通常小于 它们编目的文档和索引通常在RAM或 按顺序位于磁盘上


关于这种方法的更多信息:

重新表述问题的一种方法是,如果文档分别为16mb和16kb,100万个文档查询是否需要更长的时间

根据我自己的经验,如果我错了,请纠正我,文档大小越小,查询速度越快

我对500k文档和25k文档进行了查询,25k查询速度明显更快,从几毫秒到1-3秒不等。在生产中,时差约为2-10倍

文档大小发挥作用的一个方面是查询排序,在这种情况下,文档大小将影响查询本身是否运行。我已经多次达到这一限制,尝试对多达2k个文档进行排序

以下是一些解决方案的更多参考:

最终,最终受害的是最终用户

当我试图修复导致性能异常缓慢的大型查询时。我通常会发现自己创建了一个包含数据子集的新集合,并使用了大量查询条件以及排序和限制


希望这有帮助

我只是想分享一下我在MongoDB中处理大型文档的经验<不要这样做

我们犯了一个错误,允许用户在文档中包含以base64编码的文件(通常是图像和屏幕截图)。我们最终收集了大约500k个文档,每个文档的大小从2MB到10MB不等

在此集合中进行简单聚合将导致集群崩溃

在MongoDB中,聚合查询可能非常繁重,特别是对于这样的大型文档。聚合中的索引只能在某些情况下使用,因为我们需要
$group
,所以没有使用索引,MongoDB必须扫描所有文档

在具有较小文档的集合中执行完全相同的查询
const numberOfDocuments = 1024;

// 2MB string x 1024 ~ 2GB collection
const bigString = 'a'.repeat(2 * 1024 * 1024);

// generate and insert documents in two collections: shortDocuments and
// largeDocuments;
for (let i = 0; i < numberOfDocuments; i++) {
  let doc = {};
  // field a: integer between 0 and 10, equal in both collections;
  doc.a = ~~(Math.random() * 10);

  // field b: single character between a to j, equal in both collections;
  doc.b = String.fromCharCode(97 + ~~(Math.random() * 10));

  //insert in smallDocuments collection
  db.smallDocuments.insert(doc);

  // field c: big string, present only in bigDocuments collection;
  doc.c = bigString;

  //insert in bigDocuments collection
  db.bigDocuments.insert(doc);
}
const numbersToQuery = [];

// generate 100 random numbers to query documents using field 'a':
for (let i = 0; i < 100; i++) {
  numbersToQuery.push(~~(Math.random() * 10));
}

const smallStart = Date.now();
numbersToQuery.forEach(number => {
  // query using inequality conditions: slower than equality
  const docs = db.smallDocuments
    .find({ a: { $ne: number } }, { a: 1, b: 1 })
    .toArray();
});
print('Small:' + (Date.now() - smallStart) + ' ms');

const bigStart = Date.now();
numbersToQuery.forEach(number => {
  // repeat the same queries in the bigDocuments collection; note that the big field 'c'
  // is ommited in the projection
  const docs = db.bigDocuments
    .find({ a: { $ne: number } }, { a: 1, b: 1 })
    .toArray();
});
print('Big: ' + (Date.now() - bigStart) + ' ms');
Small: 1976 ms
Big: 19835 ms
Small: 2258 ms
Big: 4761 ms