[英]MongoDB: does document size affect query performance?
假设有一个由 MongoDB 数据库支持的手机游戏,该数据库包含一个包含数百万个文档的User
集合。
现在假设必须与用户相关联的几十个属性 - 例如, Friend
文档的_id
值数组、他们的用户名、照片、 Game
文档的_id
值数组、上次登录日期、游戏货币数量等, 等等..
我担心的是,在数百万个用户文档上创建和更新大型、不断增长的数组是否会给每个用户文档增加任何“权重”,和/或整个系统的速度变慢。
我们可能永远不会让每个文档超过 16mb,但我们可以肯定地说,如果我们直接存储这些不断增长的列表,我们的文档会大 10-20 倍。
问题:这甚至是 MongoDB 中的问题吗? 如果您的查询使用投影和索引等正确管理,文档大小是否重要? 我们是否应该积极修剪文档大小,例如引用外部列表与直接嵌入_id
值列表?
换句话说:如果我想要一个用户的last_login
值,如果我的User
文档是 100kb 和 5mb,那么项目/选择仅last_login
字段的查询会有什么不同吗?
或者:如果我想找到具有特定last_login
值的所有用户,文档大小会影响那种查询吗?
重新表述这个问题的一种方法是说,如果每个文档的大小为 16mb 与 16kb,那么 100 万个文档查询是否需要更长的时间。
如果我错了,请纠正我,根据我自己的经验,文档大小越小,查询速度越快。
我已经对 500k 文档和 25k 文档进行了查询,25k 查询明显更快 - 从几毫秒到 1-3 秒不等。 在生产中,时间差大约是 2x-10x。
文档大小发挥作用的一方面是查询排序,在这种情况下,文档大小将影响查询本身是否会运行。 我已经多次达到这个限制,试图对 2k 的文档进行排序。
这里有一些解决方案的更多参考: https : //docs.mongodb.org/manual/reference/limits/#operations https://docs.mongodb.org/manual/reference/operator/aggregation/sort/#sort-memory-限制
归根结底,受苦的是最终用户。
当我尝试修复导致性能低得令人无法接受的大型查询时。 我通常会发现自己创建一个包含数据子集的新集合,并使用大量查询条件以及排序和限制。
希望这可以帮助!
首先,您应该花一点时间阅读 MongoDB 如何根据填充因子和 powerof2sizes 分配存储文档:
http://docs.mongodb.org/manual/core/storage/ http://docs.mongodb.org/manual/reference/command/collStats/#collStats.paddingFactor
简单地说,MongoDB 在存储原始文档时会尝试分配一些额外的空间以允许增长。 Powerof2sizes 分配成为 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 中可用或按顺序位于磁盘上。
更多关于这种方法的信息: http : //docs.mongodb.org/manual/tutorial/create-indexes-to-support-queries/
只是想分享我在 MongoDB 中处理大文档时的经验......不要这样做!
我们犯了一个错误,允许用户在文档中包含以 base64 编码的文件(通常是图像和屏幕截图)。 我们最终得到了大约 50 万个文档的集合,每个文档的大小从 2 Mb 到 10 Mb。
在这个集合中做一个简单的聚合会导致集群崩溃!
MongoDB 中的聚合查询可能非常繁重,尤其是像这样的大型文档。 聚合中的索引只能在某些情况下使用,并且由于我们需要$group
,因此没有使用索引,MongoDB 必须扫描所有文档。
在具有较小尺寸文档的集合中完全相同的查询执行速度非常快,并且资源消耗不是很高。
因此,在 MongoDB 中查询大文档会对性能产生很大影响,尤其是聚合。
此外,如果您知道文档在创建后会继续增长(例如,在给定实体(文档)中包含日志事件),请考虑为这些子项创建一个集合,因为大小在将来也会成为问题。
布鲁诺。
简短的回答:是的。
长答案:它将如何影响查询取决于许多因素,例如查询的性质、可用内存和索引大小。
你能做的最好的事情就是测试。
下面的代码将生成两个名为 smallDocuments 和 bigDocuments 的集合,每个集合有 1024 个文档,不同之处仅在于包含大字符串和 _id 的字段“c”。 bigDocuments 集合将有大约 2GB,所以运行它时要小心。
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);
}
您可以将此代码放在一个文件中(例如 create-test-data.js)并直接在 mongoshell 中运行它,输入以下命令:
mongo testDb < create-test-data.js
这将需要一段时间。 之后,您可以执行一些测试查询,例如:
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
在两个集合中索引字段 'a' 后,使用.createIndex({ a: 1 })
:
Small: 2258 ms
Big: 4761 ms
这表明对大文档的查询速度较慢。 使用索引,bigDocuments 的结果时间比 smallDocuments 大 100% 以上。
我在使用 MongoDB 时遇到大文档中的文本查询问题:撇号-cms 中的自动完成和文本搜索内存问题:需要想法
这里有一些我在 ApostropheCMS 中编写的用于生成示例数据的代码,以及一些测试结果: https : //github.com/souzabrs/misc/tree/master/big-pieces 。
这与其说是 MongoDB 内部问题,不如说是数据库设计问题。 我认为 MongoDB 就是这样做的。 但是,在其文档中有更明显的解释会很有帮助。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.