ruby - sequel model
¿Hay algún ORM de Ruby que use cursores o búsqueda inteligente? (5)
Estoy buscando un ORM de Ruby para reemplazar ActiveRecord. He estado buscando en Sequel y DataMapper. Se ven bastante bien, sin embargo, ninguno de ellos parece hacer lo básico: no cargar todo en la memoria cuando no lo necesitas.
Quiero decir que he intentado lo siguiente (o equivalente) en ActiveRecord y Sequel en la tabla con muchas filas:
posts.each { |p| puts p }
Ambos se vuelven locos de memoria. Parecen cargar todo en la memoria en lugar de buscar cosas cuando es necesario. Utilicé los find_in_batches
en ActiveRecord, pero no es una solución aceptable:
- ActiveRecord no es una solución aceptable porque tuvimos demasiados problemas con ella.
¿Por qué mi código debe ser consciente de un mecanismo de paginación? Estoy feliz de configurar en alguna parte el tamaño de la página, pero eso es todo. Con
find_in_batches
necesitas hacer algo como:post.find_in_batches {| batch | batch.each {| p | pone p}}
Pero eso debería ser transparente.
Entonces, ¿hay en algún lugar un ORM de Ruby confiable que realice la búsqueda correctamente?
Actualizar:
Como mencionó Sergio, en Rails 3 puedes usar find_each
que es exactamente lo que quiero. Sin embargo, como ActiveRecord no es una opción, excepto si alguien realmente puede convencerme de que lo use, las preguntas son:
- ¿Qué ORMs soportan el equivalente de find_each?
- ¿Cómo hacerlo?
- ¿Por qué necesitamos un
find_each
, mientras quefind
debería hacerlo, no debería?
ActiveRecord en realidad tiene un modo por lotes casi transparente:
User.find_each do |user|
NewsLetter.weekly_deliver(user)
end
El Dataset#each
de Dataset#each
Sequel Dataset#each
filas individuales a la vez, pero la mayoría de los controladores de base de datos cargarán primero el resultado completo en la memoria.
Si está utilizando el adaptador Postgres de Sequel, puede elegir usar cursores reales:
posts.use_cursor.each{|p| puts p}
Esto recupera 1000 filas a la vez de forma predeterminada, pero puede usar una opción para especificar la cantidad de filas a agarrar por búsqueda de cursor:
posts.use_cursor(:rows_per_fetch=>100).each{|p| puts p}
Si no está utilizando el adaptador Postgres de Sequel, puede usar la extensión de paginación de Sequel:
Sequel.extension :pagination
posts.order(:id).each_page(1000){|ds| ds.each{|p| puts p}}
Sin embargo, al igual que find_in_batches
/ find_each
, esto hace consultas separadas, por lo que debe tener cuidado si hay modificaciones simultáneas en el conjunto de datos que está recuperando.
La razón por la que este no es el valor predeterminado en Sequel es probablemente la misma razón por la que no es el valor predeterminado en ActiveRecord, que es que no es un buen valor predeterminado en el caso general. Solo las consultas con conjuntos de resultados grandes realmente deben preocuparse, y la mayoría de las consultas no devuelven conjuntos de resultados grandes.
Al menos con el soporte del cursor del adaptador Postgres, es bastante fácil convertirlo en el predeterminado para su modelo:
Post.dataset = Post.dataset.use_cursor
Para la extensión de paginación, realmente no puedes hacer eso, pero puedes envolverlo en un método que lo haga casi transparente.
Este código funciona más rápido que find_in_batches en ActiveRecord
id_max = table.get(:max[:id])
id_min = table.get(:min[:id])
n=1000
(0..(id_max-id_min)/n).map.each do |i|
table.filter(:id >= id_min+n*i, :id < id_min+n*(i+1)).each {|row|}
end
Sequel.extension :pagination
posts.order(:id).each_page(1000) do |ds|
ds.each { |p| puts p }
end
¡Es muy muy lento en mesas grandes!
Resulta claro, miró el cuerpo del método: http://sequel.rubyforge.org/rdoc-plugins/classes/Sequel/Dataset.html#method-i-paginate
# File lib/sequel/extensions/pagination.rb, line 11
def paginate(page_no, page_size, record_count=nil)
raise(Error, "You cannot paginate a dataset that already has a limit") if @opts[:limit]
paginated = limit(page_size, (page_no - 1) * page_size)
paginated.extend(Pagination)
paginated.set_pagination_info(page_no, page_size, record_count || count)
end