我们最近为我们的一个主要系列收录了> 2百万条记录,现在我们开始对该系列的主要性能问题感到不满。
它们在集合中的文档有大约8个字段,您可以使用UI过滤,结果应该按记录处理的时间戳字段排序。
我已经添加了几个复合索引与过滤的字段和时间戳
例如:
db.events.ensureIndex({somefield: 1, timestamp:-1})
我也添加了几个索引,同时使用几个过滤器,希望实现更好的性能。但一些过滤器仍然需要很长的时间来执行。
我确保使用解释查询确实使用我创建的索引,但性能仍然不够好。
我想知道如果分片是现在的方式去,但我们很快就会开始有每天约100万新的记录在该集合..所以我不知道如果它会扩展好..
编辑:查询的示例:
> db.audit.find({'userAgent.deviceType': 'MOBILE', 'user.userName': {$in: [[email protected]']}}).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" : [
[
"[email protected]",
"[email protected]"
]
],
"timestamp" : [
[
{
"$maxElement" : 1
},
{
"$minElement" : 1
}
]
]
},
"server" : "yarin:27017"
}
请注意deviceType在我的集合中只有2个值。
这是在大海捞针。对于那些性能不佳的查询,我们需要一些explain()输出。不幸的是,即使这将解决问题只有那个特定的查询,所以这里是一个如何处理这个策略:
>确保它不是因为RAM不足和过度分页
>启用DB profiler(使用db.setProfilingLevel(1,timeout),其中timeout是查询或命令需要的毫秒数的阈值,将记录任何更慢的阈值)
>检查db.system.profile中的慢查询,并使用explain()手动运行查询
>尝试识别explain()输出中的慢操作,例如scanAndOrder或大型nscanned等。
>关于查询的选择性的原因以及是否可以使用索引来完善查询。如果没有,请考虑不允许最终用户的过滤器设置,或给他一个警告对话框,操作可能很慢。
一个关键的问题是,你显然允许用户随意组合过滤器。没有指数交叉,这将大大地炸毁所需的指数的数量。
另外,盲目地在每个可能的查询上抛出索引是一个非常糟糕的策略。重要的是构造查询并确保索引字段具有足够的选择性。
假设您对状态为“有效”的所有用户以及其他一些条件进行了查询。但是在500万用户中,300万是活跃的,200万是不,所以超过500万条目只有两个不同的价值观。这样的索引通常不会帮助。最好先搜索其他条件,然后扫描结果。平均而言,当返回100个文档时,您必须扫描167个文档,这不会对性能造成太大的影响。但这不是那么简单。如果主要标准是用户的joined_at日期,并且用户停止使用时间的可能性很高,那么在找到100个匹配项之前,您可能需要扫描数千个文档。
因此,优化很大程度上取决于数据(不仅是其结构,而且数据本身),其内部相关性和查询模式。
当数据对于RAM太大时,事情变得更糟,因为那时索引很大,但是扫描(或者直接返回)结果可能需要从磁盘随机取得大量数据,这需要很多时间。
控制这种情况的最佳方法是限制不同查询类型的数量,禁止对低选择性信息的查询,并尝试阻止对旧数据的随机访问。
如果一切都失败了,如果你真的需要过滤器的灵活性,可能值得考虑一个单独的搜索数据库,支持索引交集,从那里获取mongo id,然后使用$ in得到mongo的结果。但这是充满了自己的危险。
– 编辑 –
您发布的解释是扫描低选择性字段的问题的一个漂亮的例子。显然,[email protected],查找这些文档并按时间戳降序排序是相当快的,因为它是由高选择性索引支持的。不幸的是,由于只有两种设备类型,mongo需要扫描30060文档,以找到第一个匹配’mobile’。
我假设这是某种类型的网络跟踪,用户的使用模式使查询慢(如果他每天切换移动和Web,查询会很快)。
使得特定查询更快可以使用包含设备类型的复合索引来完成,例如使用
a) ensureIndex({'username': 1, 'userAgent.deviceType' : 1, 'timestamp' :-1})
要么
b) ensureIndex({'userAgent.deviceType' : 1, 'username' : 1, 'timestamp' :-1})
不幸的是,这意味着像find({“username”:“foo”})。sort({“timestamp”:-1}); can’t use the same index anymore,因此,如所描述的,索引的数量将增长非常快。
我恐怕没有非常好的解决方案这使用mongodb在这个时候。