我们最近为我们的一个主要收藏品创下了大约200万条记录,现在我们开始因该系列的主要性能问题而受到影响。
他们在集合中的文档有大约8个字段,您可以使用UI进行过滤,结果应该按照处理记录的时间戳字段进行排序。
我在过滤字段和时间标记中添加了几个复合索引 e.g:
db.events.ensureIndex({somefield: 1, timestamp:-1})
我还添加了几个索引,可以同时使用多个过滤器,以期获得更好的性能。但是有些过滤器仍然需要很长时间才能完成。
我已经确定使用解释查询确实使用了我创建的索引,但性能仍不够好。
我想知道分片是否是现在的方式..但我们很快就会开始在该系列中每天创造约100万条新记录......所以我不确定它是否会很好地扩展...
编辑:查询示例:
> db.audit.find({'userAgent.deviceType': 'MOBILE', 'user.userName': {$in: ['nickey@acme.com']}}).sort({timestamp: -1}).limit(25).explain()
{
"cursor" : "BtreeCursor user.userName_1_timestamp_-1",
"isMultiKey" : false,
"n" : 0,
"nscannedObjects" : 30060,
"nscanned" : 30060,
"nscannedObjectsAllPlans" : 120241,
"nscannedAllPlans" : 120241,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 1,
"nChunkSkips" : 0,
"millis" : 26495,
"indexBounds" : {
"user.userName" : [
[
"nickey@acme.com",
"nickey@acme.com"
]
],
"timestamp" : [
[
{
"$maxElement" : 1
},
{
"$minElement" : 1
}
]
]
},
"server" : "yarin:27017"
}
请注意,deviceType在我的收藏中只有2个值。
答案 0 :(得分:63)
这是在大海捞针。对于那些效果不佳的查询,我们需要explain()
的一些输出。不幸的是,即使这样也只能解决特定查询的问题,所以这里有一个如何处理这个问题的策略:
db.setProfilingLevel(1, timeout)
,其中timeout
是查询或命令所用毫秒数的阈值,将记录任何较慢的内容。db.system.profile
中的慢查询,然后使用explain()
explain()
输出中的慢速操作,例如scanAndOrder
或大nscanned
等。一个关键问题是,您显然允许用户随意组合过滤器。如果没有索引交叉,那将大大夸大所需索引的数量。
此外,盲目地在每个可能的查询中抛出索引是一个非常糟糕的策略。构建查询并确保索引字段具有足够的选择性非常重要。
假设您查询了status
“有效”的所有用户以及其他一些条件。但在500万用户中,300万用户活跃,200万用户没有,因此超过500万用户只有两个不同的值。这样的指数通常没有帮助。最好先搜索其他条件,然后扫描结果。平均而言,当返回100个文档时,您将需要扫描167个文档,这不会对性能造成太大影响。但事情并非那么简单。如果主要标准是用户的joined_at
日期以及用户停止使用时间的可能性很高,那么在找到一百个匹配项之前,您可能最终必须扫描数千个文档。
因此,优化在很大程度上取决于数据(不仅是结构,还包括数据本身),其内部关联和查询模式< / em>的
当数据对于RAM来说太大时情况会变得更糟,因为那时,索引很棒,但扫描(甚至简单地返回)结果可能需要从磁盘中随机取出大量数据,这需要大量的时间。
控制此问题的最佳方法是限制不同查询类型的数量,禁止对低选择性信息进行查询,并尝试阻止对旧数据的随机访问。
如果所有其他方法都失败了,如果你真的需要在过滤器中有这么大的灵活性,那么考虑一个支持索引交叉的单独搜索DB,从那里获取mongo id然后使用{{1从mongo获取结果 - 可能是值得的。 }}。但这充满了自己的危险。
- 编辑 -
您发布的解释是扫描低选择性字段问题的一个很好的例子。显然,有很多关于“nickey@acme.com”的文件。现在,查找这些文档并按时间戳降序排序非常快,因为它受到高选择性索引的支持。不幸的是,由于只有两种设备类型,mongo需要扫描30060个文档才能找到第一个匹配“移动”的文档。
我认为这是某种网络跟踪,用户的使用模式使查询变慢(他每天都会切换移动和网络,查询会很快)。
使用包含设备类型的复合索引可以更快地完成此特定查询,例如使用
$in
或
a) ensureIndex({'username': 1, 'userAgent.deviceType' : 1, 'timestamp' :-1})
不幸的是,这意味着像b) ensureIndex({'userAgent.deviceType' : 1, 'username' : 1, 'timestamp' :-1})
can't use the same index anymore之类的查询,因此,如上所述,索引数量会迅速增长。
我担心此时使用mongodb并没有很好的解决方案。
答案 1 :(得分:0)
Mongo每个查询只使用1个索引。 因此,如果要对2个字段进行过滤,mongo将使用其中一个字段的索引,但仍需要扫描整个子集。
这意味着基本上您需要为每种类型的查询提供索引才能获得最佳性能。
根据您的数据,每个字段有一个查询并在您的应用中处理结果可能不是一个坏主意。 这样,您只需要在每个字段上使用索引,但可能需要处理太多数据。
答案 2 :(得分:0)
如果你使用$ in,mongodb永远不会使用INDEX。通过删除此$ in来更改您的查询。它应该使用索引,它会提供比你之前更好的性能。