MongoDB查询500多万条logging的性能
我们最近为我们的一个主要collections品创下了超过200万的唱片,现在我们开始为这个collections品的主要性能问题而苦恼。
他们在集合中的文件有大约8个字段,您可以通过使用UI进行过滤,结果应该按时间戳字段对logging进行分类。
我已经添加了几个复合索引与过滤的字段和时间戳例如:
db.events.ensureIndex({somefield: 1, timestamp:-1})
我还添加了几个索引,一次使用几个filter,希望取得更好的性能。 但有些filter仍然需要很长的时间来执行。
我已经确保使用解释查询确实使用我创build的索引,但性能仍然不够好。
我想知道分片是否是现在的走向..但是我们很快就会开始在这个集合中每天有大约一百万个新logging..所以我不确定它是否能够很好地扩展。
编辑:查询的例子:
> 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个值。
这是在大海捞针。 对于执行不好的查询,我们需要一些explain()
输出。 不幸的是,即使这样也只能解决这个特定查询的问题,所以下面是一个如何处理这个问题的策略:
- 确保这不是因为内存不足和分页过多
- 启用数据库分析器(使用
db.setProfilingLevel(1, timeout)
,其中timeout
是查询或命令所用毫秒数的阈值,将会logging任何较慢的数据) - 检查
db.system.profile
的慢查询,并使用explain()
手动运行查询 - 尝试识别
explain()
输出中的缓慢操作,例如scanAndOrder
或大型scanAndOrder
等。 - 关于查询select性的原因以及是否可以使用索引完善查询。 如果不是,请考虑禁止最终用户的filter设置,或者给他一个警告对话框,说明操作可能很慢。
一个关键的问题是,你显然允许你的用户随意组合filter。 如果没有索引相交,那么将会大大增加所需索引的数量。
另外,在每个可能的查询中盲目地抛出一个索引是一个非常糟糕的策略。 查询的结构很重要,并确保索引字段具有足够的select性 。
比方说,你有一个查询status
“活跃”和一些其他标准的所有用户。 但在500万用户中,有300万是活跃的,200万是不活跃的,所以超过500万的用户只有两个不同的值。 这样的指数通常不会有帮助。 最好先search其他标准,然后扫描结果。 平均而言,当返回100个文档时,您将不得不扫描167个文档,这不会影响性能。 但事情并不那么简单。 如果主要标准是用户的joined_at
date,并且用户随时间停止使用的可能性很高,则最终可能在查找数百个匹配之前扫描数千个文档。
因此,优化很大程度上取决于数据(不仅是其结构 ,还有数据本身 ),其内部相关性和查询模式 。
当数据对于RAM来说太大时,事情会变得更糟,因为这样做有一个好的索引,但是扫描(甚至是简单地返回)结果可能需要从磁盘中随机获取大量数据,这需要很多时间。
控制这种情况的最佳方式是限制不同查询types的数量,不允许对低select性信息进行查询,并尝试防止随机访问旧数据。
如果一切都失败了,如果你真的需要filter的灵活性,那么考虑一个支持索引交叉的单独search数据库可能是值得的,从这里获取mongo ID,然后使用$in
从mongo获得结果。 但这充满了自身的危险。
– 编辑 –
您发布的解释是扫描低select性字段的一个很好的例子。 显然,有很多“nickey@acme.com”的文件。 现在,查找这些文档并按时间戳降序排列它们是相当快的,因为它具有高select性索引的支持。 不幸的是,由于只有两种设备types,mongo需要扫描30060个文档才能find第一个匹配“mobile”的设备。
我认为这是一种networking跟踪,用户的使用模式使查询速度慢(他会每天切换手机和networking,查询会很快)。
使用包含设备types的复合索引(例如using)可以更快地完成这个特定的查询
a) ensureIndex({'username': 1, 'userAgent.deviceType' : 1, 'timestamp' :-1})
要么
b) ensureIndex({'userAgent.deviceType' : 1, 'username' : 1, 'timestamp' :-1})
不幸的是,这意味着像find({"username" : "foo"}).sort({"timestamp" : -1});
不能再使用相同的索引 ,所以, 如上所述 ,索引的数量将会很快增长。
恐怕目前没有很好的解决scheme,使用mongodb。
Mongo每个查询只使用1个索引。 所以,如果你想过滤两个字段,mongo将使用索引和其中一个字段,但仍然需要扫描整个子集。
这意味着基本上,您需要每种查询types的索引以获得最佳性能。
根据您的数据,每个字段有一个查询并不是一个坏主意,并在您的应用程序中处理结果。 这样你只需要在每个字段上都有索引,但是可能需要太多的数据来处理。
如果您使用$ in,mongodb从不使用INDEX。 改变你的查询,通过删除这个$ in。 它应该使用索引,它会比以前得到更好的性能。