ruby-on-rails - mongodb ruby on rails
Encontrar registros de mongoDB en lotes(usando el adaptador ruby mongoide) (6)
Utilizando rails 3 y mongoDB con el adaptador mongoid, ¿cómo puedo encontrar lotes en el mongo DB? Necesito tomar todos los registros en una colección particular de DB mongo e indexarlos en solr (índice inicial de datos para la búsqueda).
El problema que tengo es que hacer Model.all toma todos los registros y los almacena en la memoria. Luego, cuando proceso sobre ellos e indexo en sol, mi memoria se consume y el proceso muere.
Lo que intento hacer es agrupar el hallazgo en mongo para poder iterar más de 1,000 registros a la vez, pasarlos a solr para indexar, y luego procesar los siguientes 1,000, etc.
El código que tengo actualmente hace esto:
Model.all.each do |r|
Sunspot.index(r)
end
Para una colección que tiene aproximadamente 1,5 millones de registros, esto consume más de 8 GB de memoria y mata el proceso. En ActiveRecord, hay un método find_in_batches que me permite dividir las consultas en lotes manejables que evitan que la memoria se salga de control. Sin embargo, no puedo encontrar nada como esto para mongoDB / mongoid.
ME GUSTARÍA poder hacer algo como esto:
Model.all.in_batches_of(1000) do |batch|
Sunpot.index(batch)
end
Eso aliviará mis problemas de memoria y las dificultades de consulta al solo hacer un conjunto de problemas manejable cada vez. Sin embargo, la documentación es escasa al realizar búsquedas por lotes en mongoDB. Veo mucha documentación sobre cómo hacer inserciones de lotes, pero no lotes.
Como dijo @RyanMcGeary, no necesita preocuparse por la búsqueda por lotes. Sin embargo, indexar objetos de uno en uno es mucho más lento que procesarlos en lotes.
Model.all.to_a.in_groups_of(1000, false) do |records|
Sunspot.index! records
end
Lo siguiente funcionará para ti, solo inténtalo
Model.all.in_groups_of(1000, false) do |r|
Sunspot.index! r
end
No estoy seguro del procesamiento por lotes, pero puedes hacerlo de esta manera
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
Pero si está buscando una solución perfecta a largo plazo, no lo recomendaría. Permítanme explicar cómo manejé el mismo escenario en mi aplicación. En lugar de hacer trabajos por lotes,
He creado un trabajo resque que actualiza el índice de 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
fin
Después de agregar el artículo, simplemente coloqué una entrada en la cola de resque
Resque.enqueue(SolrUpdator, item.id.to_s)
- Eso es todo, comienza el resque y se encargará de todo
Si está iterando sobre una colección donde cada registro requiere mucho procesamiento (es decir, consultar una API externa para cada elemento), es posible que el cursor expire. En este caso, debe realizar múltiples consultas para no dejar el cursor abierto.
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
Aquí hay un método de ayuda que puede usar para agregar la funcionalidad de procesamiento por lotes. Se puede usar así:
Post.all.order_by(:id => 1).in_batches_of(7).each_with_index do |post, index|
# call external slow API
end
Solo asegúrate SIEMPRE tienes un order_by en tu consulta. De lo contrario, la búsqueda podría no hacer lo que usted desea. También me quedaría con lotes de 100 o menos. Como se dijo en la respuesta aceptada, Mongoid consulta en lotes de 100, por lo que nunca querrá dejar el cursor abierto mientras realiza el procesamiento.
También es más rápido enviar lotes a las manchas solares. Así es como lo hago:
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
: evita que el cursor se desconecte (después de 10 min, de forma predeterminada)
only
: selecciona solo el id y los campos, que en realidad están indexados
batch_size
: busca 1000 entradas en lugar de 100
Con Mongoid, no necesita procesar manualmente la consulta.
En Mongoid, Model.all
devuelve una instancia de Mongoid::Criteria
. Al invocar #each
en este Criterio, se #each
una instancia del cursor del controlador de Mongo y se utiliza para iterar sobre los registros. Este cursor subyacente del controlador Mongo ya agrupa todos los registros. Por defecto, batch_size
es 100.
Para obtener más información sobre este tema, lea este comentario del autor y mantenedor de Mongoid .
En resumen, puedes hacer esto:
Model.all.each do |r|
Sunspot.index(r)
end