草庐IT

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

coder 2023-04-23 原文

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

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

我添加了几个带有过滤字段和时间戳的复合索引
例如:

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

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

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

我想知道分片现在是否可行..但我们很快就会开始在该集合中每天有大约 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 个值。

最佳答案

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

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

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

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

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

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

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

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

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

    -- 编辑 --

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

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

    可以使用包含设备类型的复合索引来加快此特定查询的速度,例如使用
    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 没有很好的解决方案。

    关于MongoDB 超过 500 万条记录的查询性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19559405/

    有关MongoDB 超过 500 万条记录的查询性能的更多相关文章

    1. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

      我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

    2. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

      Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

    3. ruby-on-rails - 在 Rails 和 ActiveRecord 中查询时忽略某些字段 - 2

      我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr

    4. ruby-on-rails - Rails 5 Active Record 记录无效错误 - 2

      我有两个Rails模型,即Invoice和Invoice_details。一个Invoice_details属于Invoice,一个Invoice有多个Invoice_details。我无法使用accepts_nested_attributes_forinInvoice通过Invoice模型保存Invoice_details。我收到以下错误:(0.2ms)BEGIN(0.2ms)ROLLBACKCompleted422UnprocessableEntityin25ms(ActiveRecord:4.0ms)ActiveRecord::RecordInvalid(Validationfa

    5. ruby-on-rails - 事件记录 : Select max of limit - 2

      我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).

    6. sql - 查询忽略时间戳日期的时间范围 - 2

      我正在尝试查询我的Rails数据库(Postgres)中的购买表,我想查询时间范围。例如,我想知道在所有日期的下午2点到3点之间进行了多少次购买。此表中有一个created_at列,但我不知道如何在不搜索特定日期的情况下完成此操作。我试过:Purchases.where("created_atBETWEEN?and?",Time.now-1.hour,Time.now)但这最终只会搜索今天与那些时间的日期。 最佳答案 您需要使用PostgreSQL'sdate_part/extractfunction从created_at中提取小时

    7. Ruby 守护进程导致 ActiveRecord 记录器 IOError - 2

      我目前正在用Ruby编写一个项目,它使用ActiveRecordgem进行数据库交互,我正在尝试使用ActiveRecord::Base.logger记录所有数据库事件具有以下代码的属性ActiveRecord::Base.logger=Logger.new(File.open('logs/database.log','a'))这适用于迁移等(出于某种原因似乎需要启用日志记录,因为它在禁用时会出现NilClass错误)但是当我尝试运行包含调用ActiveRecord对象的线程守护程序的项目时脚本失败并出现以下错误/System/Library/Frameworks/Ruby.frame

    8. ruby-on-rails - 在 Rails 中更高效地查找或创建多条记录 - 2

      我有一个应用需要发送用户事件邀请。当用户邀请friend(用户)参加事件时,如果尚不存在将用户连接到该事件的新记录,则会创建该记录。我的模型由用户、事件和events_user组成。classEventdefinvite(user_id,*args)user_id.eachdo|u|e=EventsUser.find_or_create_by_event_id_and_user_id(self.id,u)e.save!endendend用法Event.first.invite([1,2,3])我不认为以上是完成我的任务的最有效方法。我设想了一种方法,例如Model.find_or_cr

    9. Ruby 的数字方法性能 - 2

      我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0

    10. ruby-on-rails - solr 清理查询 - 2

      我在Rails上使用带有ruby​​的solr。一切正常,我只需要知道是否有任何现有代码来清理用户输入,比如以?开头的查询。或* 最佳答案 我不知道执行此操作的任何代码,但理论上可以通过查看parsingcodeinLucene来完成并搜索thrownewParseException(只有16个匹配!)。在实践中,我认为您最好只捕获代码中的任何solr异常并显示“无效查询”消息或类似信息。编辑:这里有几个“sanitizer”:http://pivotallabs.com/users/zach/blog/articles/937-s

    随机推荐