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.
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
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
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.
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
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
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
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