Encontrar registros MongoDB en lotes (usando el adaptador mongoid ruby)


Usando rails 3 y MongoDB con el adaptador mongoid, ¿cómo puedo encontrar por lotes la base de datos mongo? Necesito tomar todos los registros de una colección de mongo DB en particular e indexarlos en solr (índice inicial de datos para la búsqueda).

El problema que tengo es que hacer Modelo.todos agarra todos los registros y los almacena en la memoria. Luego, cuando proceso sobre ellos e índice en solr, mi memoria se come y el proceso muere.

Lo que estoy tratando de hacer es lote el hallazgo en mongo así que puedo 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 alrededor de 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 evita 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ía mis problemas de memoria y dificultades de consulta haciendo solo un conjunto de problemas manejable cada vez. Sin embargo, la documentación es escasa al hacer búsquedas por lotes en MongoDB. Veo mucha documentación sobre cómo hacer inserciones por lotes, pero no hallazgos por lotes.

Author: ekad, 2011-08-12

6 answers

Con Mongoid, no es necesario realizar manualmente la consulta por lotes.

En Mongoid, Model.all devuelve una instancia Mongoid::Criteria. Al llamar a #each en este Criterio, se instanciará un cursor de controlador Mongo y se utilizará para iterar sobre los registros. Este cursor del controlador Mongo subyacente ya agrupa todos los registros. Por defecto el batch_size es 100.

Para más información sobre este tema, lea este comentario del autor y mantenedor de Mongoid.

En resumen, solo puede hacer esto:

Model.all.each do |r|
  Sunspot.index(r)
end
 81
Author: Ryan McGeary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-08 21:49:57

También es más rápido enviar lotes a sunspot. 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 minutos, por defecto)

only: selecciona solo el id y los campos, que en realidad están indexados

batch_size: obtenga 1000 entradas en lugar de 100

 5
Author: Mic92,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-16 19:55:01

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 se agote. En este caso, debe realizar varias 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 auxiliar 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úrese de que siempre tiene un order_by en su consulta. De lo contrario, la paginación podría no hacer lo que tú quieres. También me quedaría con lotes de 100 o menos. Como se dijo en la respuesta aceptada Consultas Mongoid en lotes de 100 por lo que nunca desea dejar el cursor abierto mientras se realiza el procesamiento.

 4
Author: HaxElit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-16 17:26:52

No estoy seguro del procesamiento por lotes, pero puede 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 recomendaría esto. 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 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 elemento, simplemente pongo una entrada en la cola resque

    Resque.enqueue(SolrUpdator, item.id.to_s)
    
  • Eso es todo, inicie el resque y se encargará de todo
 2
Author: RameshVel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-28 15:54:51

Lo siguiente funcionará para usted , solo inténtelo

Model.all.in_groups_of(1000, false) do |r|
  Sunspot.index! r
end
 0
Author: ratnakar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-25 11:22:33

Como dijo @RyanMcGeary, no necesita preocuparse por agrupar la consulta. Sin embargo, indexar objetos uno a la vez es mucho más lento que procesarlos por lotes.

Model.all.to_a.in_groups_of(1000, false) do |records|
  Sunspot.index! records
end
 -3
Author: Derek Harmel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-28 21:05:19