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でバッチ検索を実行する際のドキュメントはまばらです。バッチ挿入の実行に関するドキュメントはたくさんありますが、バッチ検索はありません。
Mongoidでは、クエリを手動でバッチ処理する必要はありません。
Mongoidでは、Model.all
はMongoid::Criteria
インスタンスを返します。この基準で#each
を呼び出すと、Mongoドライバーカーソルがインスタンス化され、レコードの反復処理に使用されます。この基礎となるMongoドライバーカーソルは既にすべてのレコードをバッチ処理しています。デフォルトでは、batch_size
は100です。
このトピックの詳細については、 Mongoidの作成者および保守者からのこのコメント を参照してください。
要約すると、これを行うことができます:
Model.all.each do |r|
Sunspot.index(r)
end
各レコードが多くの処理を必要とするコレクション(つまり、各アイテムの外部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のバッチでクエリを実行するため、処理中にカーソルを開いたままにしたくない場合があります。
バッチを黒点にも送信する方が高速です。これが私のやり方です:
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エントリをフェッチします
バッチ処理についてはわかりませんが、この方法で行うことができます
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)
以下はあなたのために働くでしょう、ちょうどそれを試してください
Model.all.in_groups_of(1000, false) do |r|
Sunspot.index! r
end