我们最近为我们的一个主要系列创下了超过200万的记录,现在我们开始因该系列的主要性能问题而受到影响 .
他们在集合中的文档有大约8个字段,您可以使用UI进行过滤,结果应该按处理记录的时间戳字段进行排序 .
我添加了几个带有过滤字段和时间标记的复合索引,例如:
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个值 .
3 回答
这是在大海捞针 . 我们需要一些
explain()
的输出,用于那些不知道如何解决这个问题的策略:确保不是因为RAM不足和分页过多
启用数据库分析器(使用
db.setProfilingLevel(1, timeout)
,其中timeout
是查询或命令所用毫秒数的阈值,将记录任何较慢的值)检查
db.system.profile
中的慢查询并使用explain()
手动运行查询尝试识别
explain()
输出中的慢速操作,例如scanAndOrder
或大nscanned
等 .关于查询选择性的原因以及是否可以使用索引改进查询 . 如果没有,请考虑禁止最终用户的过滤器设置,或者给他一个警告对话框,说明操作可能很慢 .
一个关键问题是,您显然允许用户随意组合过滤器 . 如果没有索引交叉,那将大大增加所需索引的数量 .
此外,盲目地在每个可能的查询中抛出索引是一个非常糟糕的策略 . 构建查询并确保索引字段足够 selectivity 非常重要 .
假设您使用
status
"active"以及其他一些条件查询所有用户 . 但在500万用户中,300万用户和200万用户只有两个不同的值 . 这样的索引最好先搜索其他标准,然后扫描结果 . 平均而言,当返回100份文件时,你的表现太糟糕了 . 但事情并非那么简单 . 如果主要标准是用户的日期和用户停止使用时间的可能性很高,则可能最终必须在找到一百个匹配项之前扫描数千个文档 .所以优化很大程度上取决于数据(不仅是 structure ,还有 data itself ),它的内部关联和你的查询模式 .
当数据对于RAM来说太大时情况变得更糟,因为那时,索引很好,但扫描(甚至简单地返回)结果可能需要从磁盘中随机获取大量数据,这需要花费大量时间 .
控制此问题的最佳方法是限制不同查询类型的数量,禁止对低选择性信息进行查询,并尝试阻止对旧数据的随机访问 .
如果所有其他方法都失败了,如果你真的需要在过滤器中有这么大的灵活性,那么考虑一个支持索引交叉的单独搜索DB,从那里获取mongo id然后使用
$in
从mongo获取结果可能是值得的 . 但这充满了自己的危险 .您发布的解释是扫描低选择性字段问题的一个很好的例子 . 显然,有很多关于“nickey@acme.com”的文件 . 现在,查找这些文档并按时间戳降序排序非常快,因为它受到高选择性索引的支持 . 不幸的是,由于只有两种设备类型,mongo需要扫描30060个文档才能找到第一个匹配“mobile”的文档 .
我认为这是某种网络跟踪,用户的使用模式使查询变慢(他会每天切换移动和网络,查询会很快) .
使用包含的复合索引可以更快地完成此特定查询设备类型,例如运用
要么
不幸的是,这意味着像
find({"username" : "foo"}).sort({"timestamp" : -1});
can't use the same index anymore这样的查询,因此,如上所述,索引的数量将会非常快速地增长 .我担心此时使用mongodb并没有很好的解决方案 .
Mongo每个查询只使用1个索引 . 因此,如果要对2个字段进行过滤,mongo将使用其中一个字段的索引,但仍需要扫描整个子集 .
这意味着基本上您需要为每种类型的查询提供索引才能获得最佳性能 .
根据您的数据,每个字段有一个查询并在您的应用中处理结果可能不是一个坏主意 . 这样,您只需要在每个字段上使用索引,但可能需要处理太多数据 .
如果你使用$ in,mongodb永远不会使用INDEX . 通过删除此$ in来更改您的查询 . 它应该使用索引,它会提供比你之前更好的性能 .
http://docs.mongodb.org/manual/core/query-optimization/