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


76

我们最近已经刷新了我们其中一个主要收藏的200万条记录,现在我们开始因该收藏的主要性能问题而受苦。

它们在集合中的文档大约有8个字段,您可以使用UI对其进行过滤,并且应该按记录处理的时间戳记字段对结果进行排序。

我添加了几个复合索引,其中包含过滤字段和时间戳,例如:

db.events.ensureIndex({somefield: 1, timestamp:-1})

我还添加了几个索引,可以同时使用多个过滤器,以期获得更好的性能。但是某些过滤器仍然需要花费很长时间才能执行。

我已经确保使用using来解释查询确实使用了我创建的索引,但是性能仍然不够好。

我想知道分片是否现在是要走的路..但我们很快将开始在该馆藏中每天增加约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个值。


您在使用limit参数吗?

好东西!将来我可能会遇到类似的情况,答案也可能对我有帮助。您的数据库有多少资源?在查询到200万之前,要查询这8个KV对需要多长时间?现在需要多长时间?(好奇)
anvarik

乔,是的,我当然是在使用限制,目前将结果限制为25个文档。我什至不想谈论跳过,因为我将在不久的将来将其替换为范围查询。
Yarin Miran

2
恩维尔,当收藏大约有1-2百万条记录时,我开始感觉到一些性能问题(查询时间为5-50秒)。然后,我添加了索引,并且对于<1000ms的查询,我获得了合理的性能,现在查询要花费20ms到60秒的时间,但这全都取决于被过滤字段的值分布以及索引实际有多“有用”。
Yarin Miran

哪些查询速度慢?没有过滤的简单查询是否已经很慢?还是仅由一个字段过滤的查询变慢?还是两个领域?

Answers:


71

这是在大海捞针中搜索。explain()对于那些效果不好的查询,我们需要一些输出。不幸的是,即使那样也只能解决该特定查询的问题,因此,这是一种解决方法:

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

一个关键问题是您显然允许用户随意组合过滤器。如果没有索引相交,这将极大地炸毁所需索引的数量。

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

假设您对所有具有status“活动”和其他条件的用户进行查询。但是在500万用户中,有300万处于活动状态,而200万未处于活动状态,因此,在500万以上的条目中,只有两个不同的值。这样的索引通常无济于事。最好先搜索其他条件,然后再扫描结果。平均而言,当返回100个文档时,您将必须扫描167个文档,这不会对性能造成太大影响。但这不是那么简单。如果主要标准是joined_at用户的日期,并且用户随时间停止使用的可能性很高,那么在找到一百个匹配项之前,您可能最终不得不扫描数千个文档。

因此,优化在很大程度上取决于数据(不仅是数据的结构,还包括数据本身),其内部相关性以及您的查询模式

当数据对于RAM来说太大时,情况会变得更糟,因为那样的话,拥有一个索引就很好了,但是扫描(甚至简单地返回)结果可能需要从磁盘上随机获取大量数据,这需要很多时间。

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

如果所有其他方法都失败了,并且如果您确实确实需要过滤器那么大的灵活性,那么可能值得考虑一个单独的支持索引交集的搜索数据库,从那里获取mongo id,然后使用来从mongo获取结果$in。但这充满了自己的危险。

-编辑-

您发布的说明是扫描低选择性字段问题的一个很好的例子。显然,“ nickey@acme.com”有很多文档。现在,查找这些文档并按时间戳降序对其进行排序非常快,因为高选择性索引支持该文档。不幸的是,由于只有两种设备类型,mongo需要扫描30060个文档以找到第一个与“移动”匹配的文档。

我假设这是某种Web跟踪,并且用户的使用模式使查询变慢(如果他每天都要切换移动设备和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}); 不再可以使用相同的索引,因此,如前所述,索引的数量将迅速增加。

恐怕目前没有使用mongodb的很好的解决方案。


谢谢回复!我们遇到的另一个问题是,在我们的mongo上,有多个客户端数据库,每个客户端数据库都有大量的集合。我们担心对所有这些集合进行索引会严重影响性能,因为我们需要大量的RAM才能支持来自不同用户的同时查询。您是否为此建议一个好的搜索数据库?
Yarin Miran

我想这取决于您需要的搜索功能。对于基础知识,任何支持索引交集的数据库都应该这样做。如果您需要全文本搜索,多面搜索甚至是切片和切块,那么事情就会变得棘手,并且有各种各样的工具,从SolR,Elastic Search到OLAP多维数据集。在进行此操作时,您还可以在MongoDB Jira中投票给索引交叉点:jira.mongodb.org/browse/SERVER-3071
mnemosyn

我认为我们将为此特定表使用ElasticSearch。你怎么看待这件事 ?
Yarin Miran

2
好答案。我想知道过去4.5年在这方面发生了什么变化。
Daniel Hilgarth '18

2

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

这意味着,为了获得最佳性能,基本上每种类型的查询都需要一个索引。

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


-1

如果您使用$ in,则mongodb永远不会使用INDEX。通过删除此$ in来更改查询。它应该使用索引,并且比以前获得更好的性能。

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


14
仅供参考,$ in使用索引,$ nin不使用索引。我们所经历的$ in问题是mongo对$ in中的每个值执行查询。尽管使用索引对于每个查询它的极其缓慢..
Yarin米朗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.