rails ruby-on-rails mongodb mongoid

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