批量查找 mongoDB 记录(使用 mongoid ruby​​ 适配器)

Posted

技术标签:

【中文标题】批量查找 mongoDB 记录(使用 mongoid ruby​​ 适配器)【英文标题】:Finding mongoDB records in batches (using mongoid ruby adapter) 【发布时间】:2011-10-25 20:30:50 【问题描述】:

使用带有 mongoid 适配器的 rails 3 和 mongoDB,我如何批量查找到 mongo DB?我需要获取特定 mongo DB 集合中的所有记录并在 solr(用于搜索的数据的初始索引)中对其进行索引。

我遇到的问题是 Model.all 会抓取所有记录并将它们存储到内存中。然后,当我处理它们并在 solr 中建立索引时,我的内存被吃光了,进程终止了。

我要做的是在 mongo 中批量查找,这样我就可以一次迭代 1000 多条记录,将它们传递给 solr 以进行索引,然后处理接下来的 1000 条,等等......

我目前拥有的代码是这样做的:

Model.all.each do |r|
  Sunspot.index(r)
end

对于包含大约 150 万条记录的集合,这会占用 8+ GB 的内存并终止进程。在 ActiveRecord 中,有一个 find_in_batches 方法允许我将查询分块成可管理的批次,以防止内存失控。但是,对于 mongoDB/mongoid,我似乎找不到类似的东西。

我希望能够做这样的事情:

Model.all.in_batches_of(1000) do |batch|
  Sunpot.index(batch)
end

这将通过每次只做一个可管理的问题集来缓解我的记忆问题和查询困难。但是,关于在 mongoDB 中进行批量查找的文档很少。我看到很多关于批量插入但没有批量查找的文档。

【问题讨论】:

您确定您看到与此相关的内存问题吗? Mongoid 和底层的 Mongo 驱动程序已经使用游标批处理查询。这样可以减少内存占用。 顺便说一句,您应该将接受的答案更改为@RyanMcGeary 的答案 - 然后您问题的所有未来访问者都会看到正确的答案,并且没有人不会实施驱动程序已经完成的手动优化. 【参考方案1】:

使用 Mongoid,您无需手动批量查询。

在 Mongoid 中,Model.all 返回一个 Mongoid::Criteria 实例。在此 Criteria 上调用 #each 时,将实例化 Mongo 驱动程序游标并用于迭代记录。这个底层的 Mongo 驱动游标已经批处理了所有记录。默认情况下,batch_size 为 100。

有关此主题的更多信息,请阅读this comment from the Mongoid author and maintainer。

总而言之,您可以这样做:

Model.all.each do |r|
  Sunspot.index(r)
end

【讨论】:

感谢@RyanMcGeary 的信息,天哪,我怎么错过了光标的事情,,在关于batch_size 的durran 链接中,我们如何在外部指定...? @Edmund "Hit" 可能不是在这里使用的最佳词,因为它意味着每次都重新运行查询。这是一个数据库游标。可以把它想象成以 100 个批量流式传输数据。 您答案中的@RyanMcGeary 链接已损坏。你能编辑/更正吗? @p.matsinopoulos 我花了一段时间才找到相同的评论。已经快 5 年了,Mongoid 已经从 GitHub Issues 切换到 JIRA。我想我找到了合适的评论。 对于最近版本的记录,内部批处理大小通常从 100 开始,但随后会增加以减少对数据库的调用次数。这样做的好处还在于它适用于所有可枚举的方法,因此如果您想以实际的 ruby​​ 批次(如 100 个数组)获取记录,您可以这样做:Model.all.each_slice(100) |array| ... 【参考方案2】:

如果您正在迭代一个集合,其中每条记录都需要大量处理(即为每个项目查询外部 API),则游标可能会超时。在这种情况下,您需要执行多个查询才能不让光标保持打开状态。

require 'mongoid'

module Mongoid
  class Criteria
    def in_batches_of(count = 100)
      Enumerator.new do |y|
        total = 0

        loop do
          batch = 0

          self.limit(count).skip(total).each do |item|
            total += 1
            batch += 1
            y << item
          end

          break if batch == 0
        end
      end
    end
  end
end

这是一个可用于添加批处理功能的辅助方法。可以这样使用:

Post.all.order_by(:id => 1).in_batches_of(7).each_with_index do |post, index|
  # call external slow API
end

只要确保您的查询始终有 order_by 即可。否则分页可能不会按照您的意愿进行。此外,我会坚持使用 100 个或更少的批次。正如在接受的答案中所说的那样,Mongoid 以 100 个批次进行查询,因此您在处理时永远不想让光标保持打开状态。

【讨论】:

标准上的.no_timeout 方法使您不必手动重新连接:Post.all.order_by(:id =&gt; 1).batch_size(7).no_timeout.each_with_index do ... 【参考方案3】:

将批次发送到太阳黑子也更快。 我就是这样做的:

records = []
Model.batch_size(1000).no_timeout.only(:your_text_field, :_id).all.each do |r|
  records << r
  if records.size > 1000
    Sunspot.index! records
    records.clear
  end
end
Sunspot.index! records

no_timeout:防止光标断开(默认为 10 分钟后)

only: 只选择 id 和字段,实际上是被索引的

batch_size:获取 1000 个条目而不是 100 个

【讨论】:

记得到 'Sunspot.index!循环之后的记录',否则您将不会索引最后一组 正确。我忘记复制这部分了。【参考方案4】:

我不确定批处理,但你可以这样做

current_page = 0
item_count = Model.count
while item_count > 0
  Model.all.skip(current_page * 1000).limit(1000).each do |item|
    Sunpot.index(item)
  end
  item_count-=1000
  current_page+=1
end

但如果您正在寻找一个完美的长期解决方案,我不建议您这样做。让我解释一下我如何在我的应用程序中处理相同的场景。而不是做批处理作业,

我创建了一个 resque 作业来更新 solr 索引

class SolrUpdator
 @queue = :solr_updator

 def self.perform(item_id)
   item = Model.find(item_id)
   #i have used RSolr, u can change the below code to handle sunspot
   solr = RSolr.connect :url => Rails.application.config.solr_path
   js = JSON.parse(item.to_json)
   solr.add js         
 end

结束

添加项目后,我只是将一个条目放入 resque 队列

Resque.enqueue(SolrUpdator, item.id.to_s)
就是这样,启动 resque,它会处理好一切

【讨论】:

Ramesh,您提供的第一段代码非常适合我的用例。这只是使用脚本文件一次性加载和索引数据,因此对于我的特定情况,使用 resque 可能是过度的。但批处理能力完美! 这不是必需的。 Mongoid 和底层的 Mongo 驱动程序已经使用游标批处理查询。这样可以减少内存占用。【参考方案5】:

正如@RyanMcGeary 所说,您无需担心批处理查询。但是,一次索引一个对象比批量索引对象要慢得多。

Model.all.to_a.in_groups_of(1000, false) do |records|
  Sunspot.index! records
end

【讨论】:

Model.all.to_a 会将整个集合加载到内存中。 没错,请不要这样做:当我们谈论大型数据集时,请避免一次将整个集合转换为数组:使用Model.find_each 或以任何方式批处理,但不要使用Model.all.to_a Model.find_each 不是 Mongoid 方法。您将使用 Model.all.each 代替。【参考方案6】:

以下方法对你有用,试试吧

Model.all.in_groups_of(1000, false) do |r|
  Sunspot.index! r
end

【讨论】:

正在加载内存中的所有数据库...呵呵。这样做的重点是能够批量查询文档,如果您有 400 万个文档,您将首先将它们加载到一个数组中,然后再加载另一个组数组,从而杀死您的服务器。 @rewritten 请检查上述解决方案,与我给他解释的相同。感谢您的解释 Ryan McGeary。 in_groups_of 是 Rails 的数组方法,要使用你应该将Model.all 转换为数组,完全不推荐。 -1 是警告人们不要这样做。

以上是关于批量查找 mongoDB 记录(使用 mongoid ruby​​ 适配器)的主要内容,如果未能解决你的问题,请参考以下文章

mongodb批量插入数据

mongodb增删改查 及批量操作

mongodb 查询子节点

使用aggregate在MongoDB中查找重复的数据记录

mongodb权威指南之mongodb基础知识学习记录

如何查找与嵌套键 laravel mongodb jenssegers 匹配的记录