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

时间:2011-08-12 14:03:16

标签: ruby-on-rails mongodb mongoid

将rails 3和mongoDB与mongoid适配器一起使用,如何批量查找到mongo DB?我需要获取特定mongo数据库集合中的所有记录,并在solr中索引它们(搜索数据的初始索引)。

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

我要做的是在mongo中批量查找,这样我就可以一次迭代超过1,000条记录,将它们传递给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中进行批量查找时,文档很稀疏。我看到很多关于批量插入但没有批量查找的文档。

6 个答案:

答案 0 :(得分:84)

使用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

答案 1 :(得分:5)

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

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个

答案 2 :(得分:5)

如果要迭代一个集合,其中每个记录都需要大量处理(即查询每个项目的外部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个批次进行查询,因此您不希望在进行处理时将光标保持打开状态。

答案 3 :(得分:2)

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

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

但如果你正在寻找一个完美的长时间解决方案,我不会推荐这个。让我解释一下我如何处理我的应用程序中的相同场景。而不是做批处理作业,

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

    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,它将照顾一切

答案 4 :(得分:0)

以下内容适用于您,只需尝试一下

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

答案 5 :(得分:-3)

正如@RyanMcGeary所说,您不必担心批量查询。但是,一次索引一个对象比对它们进行批处理要慢得多。

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