假设一个由MongoDB数据库支持的移动游戏,该数据库包含一个包含数百万个文档的User
集合。
现在假设必须与用户关联的几十个属性 - 例如_id
个Friend
个文档的值_id
数组,其用户名,照片,Game
个_id
个值的last_login
值数组,last_login日期,游戏内货币数量等等等。
我担心的是,在数百万个用户文档上创建和更新大型增长数组是否会为每个用户文档添加任何“权重”,和/或整个系统的速度变慢。
我们可能永远不会超过每个文件16mb,但我们可以肯定地说,如果我们直接存储这些不断增长的列表,我们的文档将增加10-20倍。
问题:这是MongoDB中的一个问题吗?如果使用投影和索引等正确管理您的查询,文档大小是否重要?我们是否应该积极修剪文档大小,例如引用外部列表与直接嵌入last_login
值列表?
换句话说:如果我想要用户的User
值,那么如果我的last_login
文档是100kb而不是5mb,那么投影/仅选择{{1}}字段的查询是否会有所不同?
或者:如果我想查找具有特定{{1}}值的所有用户,文档大小是否会影响该类查询?
答案 0 :(得分:14)
首先,您应花一点时间阅读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/
答案 1 :(得分:9)
重新解释这个问题的一种方法是,如果文件是16mb而不是16kb,则100万文档查询需要更长的时间。
如果我错了,请纠正我,根据我自己的经验,文档尺寸越小,查询就越快。
我已经对500k文档和25k文档进行了查询,25k查询明显更快 - 范围从几毫秒到1-3秒不等。在生产时,时差约为2倍-10倍。
文档大小发挥作用的一个方面是查询排序,在这种情况下,文档大小将影响查询本身是否运行。我已经多次达到这个限制,尝试排序2k文档。
此处提供更多参考资料: https://docs.mongodb.org/manual/reference/limits/#operations https://docs.mongodb.org/manual/reference/operator/aggregation/sort/#sort-memory-limit
在一天结束时,它是最终用户。
当我尝试修复大型查询时,导致性能无法接受。我经常发现自己创建了一个包含数据子集的新集合,并使用了大量的查询条件以及排序和限制。
希望这有帮助!
答案 2 :(得分:3)
只想分享我在MongoDB中处理大型文档时的经验... 不要这样做!
我们犯了一个错误,即允许用户在文档中包含以base64编码的文件(通常是图像和屏幕截图)。我们最终收集了约50万个文档,每个文档的大小从2 Mb到10 Mb。
在此集合中进行简单的聚合会降低群集!
在MongoDB中,聚合查询可能非常繁重,尤其是对于此类大型文档。聚合中的索引只能在某些情况下使用,并且由于我们需要struct MyTabBar: View {
@Binding var index: Int
var body: some View {
HStack {
Button(action: {
self.index = 0
}) {
Image(ImageText.iconHome.image)
}
Spacer(minLength: 0)
Button(action: {
self.index = 1
}) {
Image(ImageText.iconBell.image)
}
Spacer(minLength: 0)
Button(action: {
self.index = 2
}) {
Image(ImageText.iconAdd.image)
}
Spacer(minLength: 0)
Button(action: {
self.index = 3
}) {
Image(ImageText.iconSearch.image).foregroundColor(Color.red)
}
Spacer(minLength: 0)
Button(action: {
self.index = 4
}) {
Image(ImageText.iconHamburger.image)
}
}.padding(.horizontal, 26).frame(height: 56)
}
}
,因此未使用索引,因此MongoDB必须扫描所有文档。
在文档较小的集合中,完全相同的查询执行起来非常快,而且资源消耗也不是很高。
因此,在具有大型文档的MongoDB中进行查询可能会对性能产生重大影响,尤其是聚合。
此外,如果您知道文档在创建后将继续增长(例如,像在给定实体(文档)中包含日志事件一样),请考虑为这些子项创建一个集合,因为尺寸也会成为问题。未来。
布鲁诺。
答案 3 :(得分:1)
简短的回答:是的。
长答案:它将如何影响查询取决于许多因素,例如查询的性质,可用内存和索引大小。
您能做的最好的就是测试。
下面的代码将生成两个名为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:Autocomplete and text search memory issues in apostrophe-cms: need ideas
的大型文档中进行文本查询时遇到问题在ApostropheCMS中,我编写了一些代码来生成示例数据,并提供了一些测试结果:https://github.com/souzabrs/misc/tree/master/big-pieces。
这比MongoDB内部问题更多是数据库设计问题。我认为MongoDB就是这样做的。但是,在其文档中进行更明显的说明会很有帮助。