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()输出。 不幸的是,即使这样也只能解决这个特定查询的问题,所以下面是一个如何处理这个问题的策略:

  1. 确保这不是因为内存不足和分页过多
  2. 启用数据库分析器(使用db.setProfilingLevel(1, timeout) ,其中timeout是查询或命令所用毫秒数的阈值,将会logging任何较慢的数据)
  3. 检查db.system.profile的慢查询,并使用explain()手动运行查询
  4. 尝试识别explain()输出中的缓慢操作,例如scanAndOrder或大型scanAndOrder等。
  5. 关于查询select性的原因以及是否可以使用索引完善查询。 如果不是,请考虑禁止最终用户的filter设置,或者给他一个警告对话框,说明操作可能很慢。

一个关键的问题是,你显然允许你的用户随意组合filter。 如果没有索引相交,那么将会大大增加所需索引的数量。

另外,在每个可能的查询中盲目地抛出一个索引是一个非常糟糕的策略。 查询的结构很重要,并确保索引字段具有足够的select性

比方说,你有一个查询status “活跃”和一些其他标准的所有用户。 但在500万用户中,有300万是活跃的,200万是不活跃的,所以超过500万的用户只有两个不同的值。 这样的指数通常不会有帮助。 最好先search其他标准,然后扫描结果。 平均而言,当返回100个文档时,您将不得不扫描167个文档,这不会影响性能。 但事情并不那么简单。 如果主要标准是用户的joined_atdate,并且用户随时间停止使用的可能性很高,则最终可能在查找数百个匹配之前扫描数千个文档。

因此,优化很大程度上取决于数据(不仅是其结构 ,还有数据本身 ),其内部相关性和查询模式

当数据对于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。 它应该使用索引,它会比以前得到更好的性能。

http://docs.mongodb.org/manual/core/query-optimization/