MongoDB查询超过500万条记录的性能

问题描述 投票:64回答:3

我们最近为我们的一个主要系列创下了超过200万的记录,现在我们开始因该系列的主要性能问题而受到影响。

他们在集合中的文档有大约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个值。

mongodb indexing sharding
3个回答
67
投票

这是在大海捞针。对于那些表现不佳的查询,我们需要一些explain()输出。不幸的是,即使这样也只能解决特定查询的问题,所以这里有一个如何处理这个问题的策略:

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

一个关键问题是,您显然允许用户随意组合过滤器。如果没有索引交叉,那将大大增加所需索引的数量。

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

假设您对status“active”和其他一些条件的所有用户都有查询。但在500万用户中,300万用户活跃,200万用户不活跃,因此超过500万用户只有两个不同的值。这样的指数通常没有帮助。最好先搜索其他条件,然后扫描结果。平均而言,当返回100个文档时,您将需要扫描167个文档,这不会对性能造成太大影响。但事情并非那么简单。如果主要标准是用户的joined_at日期以及用户停止使用时间的可能性很高,则可能最终必须扫描数千个文档才能找到一百个匹配项。

因此,优化在很大程度上取决于数据(不仅是结构,还包括数据本身),内部关联和查询模式。

当数据对于RAM来说太大时情况变得更糟,因为那时,索引很好,但扫描(甚至简单地返回)结果可能需要从磁盘中随机获取大量数据,这需要花费大量时间。

控制此问题的最佳方法是限制不同查询类型的数量,禁止对低选择性信息进行查询,并尝试阻止对旧数据的随机访问。

如果所有其他方法都失败了,如果你真的需要在过滤器中有这么大的灵活性,那么考虑一个支持索引交叉的单独搜索DB,从那里获取mongo id然后使用$in从mongo获得结果可能是值得的。但这充满了自己的危险。

- 编辑 -

您发布的解释是扫描低选择性字段问题的一个很好的例子。显然,有很多关于“[email protected]”的文件。现在,查找这些文档并按时间戳降序排序非常快,因为它受到高选择性索引的支持。不幸的是,由于只有两种设备类型,mongo需要扫描30060个文档才能找到第一个匹配“mobile”的文档。

我认为这是某种网络跟踪,用户的使用模式使查询变慢(他会每天切换移动和网络,查询会很快)。

使用包含设备类型的复合索引可以更快地使这个特定查询更快,例如,运用

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并没有很好的解决方案。


0
投票

Mongo每个查询只使用1个索引。因此,如果要对2个字段进行过滤,mongo将使用其中一个字段的索引,但仍需要扫描整个子集。

这意味着基本上您需要为每种类型的查询提供索引才能获得最佳性能。

根据您的数据,每个字段有一个查询并在您的应用中处理结果可能不是一个坏主意。这样,您只需要在每个字段上使用索引,但可能需要处理太多数据。


0
投票

如果你使用$ in,mongodb永远不会使用INDEX。通过删除此$ in来更改您的查询。它应该使用索引,它会提供比你之前更好的性能。

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

© www.soinside.com 2019 - 2024. All rights reserved.