web-dev-qa-db-ja.com

バッチでのmongoDBレコードの検索(mongoid Rubyアダプターを使用)

Rails 3とmongoDBをmongoidアダプターで使用して、mongo DBにバッチ検索を行うにはどうすればよいですか?特定のmongo DBコレクションのすべてのレコードを取得し、solr(初期インデックス検索用データの)。

私が抱えている問題は、Model.allを実行すると、すべてのレコードが取得され、メモリに格納されることです。次に、それらを処理し、solrでインデックスを作成すると、私の記憶が食い尽くされ、プロセスが停止します。

私がやろうとしているのは、mongoで検索をバッチ処理して、一度に1,000件を超えるレコードを反復処理し、それらをsolrに渡してインデックスを作成し、次の1,000件を処理するなどです。

私が現在持っているコードはこれを行います:

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でバッチ検索を実行する際のドキュメントはまばらです。バッチ挿入の実行に関するドキュメントはたくさんありますが、バッチ検索はありません。

39
Dan L

Mongoidでは、クエリを手動でバッチ処理する必要はありません。

Mongoidでは、Model.allMongoid::Criteriaインスタンスを返します。この基準で#eachを呼び出すと、Mongoドライバーカーソルがインスタンス化され、レコードの反復処理に使用されます。この基礎となるMongoドライバーカーソルは既にすべてのレコードをバッチ処理しています。デフォルトでは、batch_sizeは100です。

このトピックの詳細については、 Mongoidの作成者および保守者からのこのコメント を参照してください。

要約すると、これを行うことができます:

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

各レコードが多くの処理を必要とするコレクション(つまり、各アイテムの外部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のバッチでクエリを実行するため、処理中にカーソルを開いたままにしたくない場合があります。

7
HaxElit

バッチを黒点にも送信する方が高速です。これが私のやり方です:

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:100の代わりに1000エントリをフェッチします

5
Mic92

バッチ処理についてはわかりませんが、この方法で行うことができます

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を開始すると、すべてが処理されます。
2
RameshVel

以下はあなたのために働くでしょう、ちょうどそれを試してください

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