2012-01-13 9 views
19

Sto cercando un Ruby ORM per sostituire ActiveRecord. Ho guardato Sequel e DataMapper. Sembrano abbastanza buoni, ma nessuno di loro sembra fare le cose di base: non caricare tutto in memoria quando non ne hai bisogno.Esistono ORM ruby ​​che usano cursori o smart fetch?

voglio dire che ho provato quanto segue (o equivalente) su ActiveRecord e Sequel sul tavolo con un sacco di righe:

posts.each { |p| puts p } 

Entrambi impazzire sulla memoria. Sembrano caricare tutto in memoria piuttosto che prendere roba quando necessario. Ho usato il find_in_batches in ActiveRecord, ma non è una soluzione accettabile:

  1. ActiveRecord non è una soluzione accettabile perché abbiamo avuto troppi problemi con esso.
  2. Perché il mio codice dovrebbe essere a conoscenza di un meccanismo di paging? Sono felice di configurare da qualche parte la dimensione della pagina, ma questo è tutto. Con find_in_batches devi fare qualcosa del tipo:

    post.find_in_batches {| batch | batch.each {| p | mette p}}

ma che dovrebbe essere trasparente.

Quindi c'è da qualche parte un Ruby ORM affidabile che esegue il recupero in modo corretto?


Aggiornamento:

Come detto Sergio, in Rails 3 è possibile utilizzare find_each che esattamente quello che voglio. Tuttavia, poiché ActiveRecord non è un'opzione, eccetto se qualcuno può davvero convincermi a usarlo, le domande sono:

  1. Quali ORM supportano l'equivalente di find_each?
  2. Come si fa?
  3. Perché abbiamo bisogno di uno find_each, mentre lo find dovrebbe farlo, non dovrebbe?

risposta

43

Sequel's Dataset#each produce singole righe alla volta, ma la maggior parte dei driver di database caricherà prima l'intero risultato in memoria.

Se si utilizza l'adattatore Postgres di Sequel, è possibile scegliere di utilizzare i cursori reali:

posts.use_cursor.each{|p| puts p} 

Questo recupera 1000 righe alla volta di default, ma è possibile utilizzare un'opzione per specificare la quantità di righe da afferrare al recupero di cursore:

posts.use_cursor(:rows_per_fetch=>100).each{|p| puts p} 

Se non si utilizza l'adattatore Postgres di Sequel, è possibile utilizzare l'estensione impaginazione di Sequel:

Sequel.extension :pagination 
posts.order(:id).each_page(1000){|ds| ds.each{|p| puts p}} 

Tuttavia, come nel caso di ActiveRecord find_in_batches/find_each, questo fa query separate, quindi è necessario fare attenzione se ci sono modifiche simultanee al set di dati che si sta recuperando.

Il motivo per cui questo non è il valore predefinito in Sequel è probabilmente la stessa ragione per cui non è il valore predefinito in ActiveRecord, ovvero che non è un valore predefinito valido nel caso generale. Solo le query con set di risultati di grandi dimensioni devono davvero preoccuparsene e la maggior parte delle query non restituisce set di risultati di grandi dimensioni.

Almeno con il supporto adattatore di Postgres cursore, è abbastanza facile per renderlo di default per il modello:

Post.dataset = Post.dataset.use_cursor 

Per l'estensione impaginazione, non si può davvero farlo, ma si può avvolgere in un metodo che lo rende per lo più trasparente.

+0

Funziona anche per MySql o è solo con Postgres? – mb14

+0

L'elemento use_cursor è solo postgres. Non sono sicuro che MySQL supporti i cursori per la restituzione dei risultati. La documentazione del cursore MySQL indica "MySQL supporta i cursori all'interno dei programmi memorizzati", vedere http://dev.mysql.com/doc/refman/5.6/en/cursors.html. –

+0

Php usa db_fetch in modo massiccio, quindi suppongo che Mysql supporti i cursori. Comunque forse non nel driver ruby ​​predefinito – mb14

3

ActiveRecord ha in realtà un quasi trasparente batch mode:

User.find_each do |user| 
    NewsLetter.weekly_deliver(user) 
end 
+0

Grazie (+1). È in Rail 3, (stiamo usando i binari 2.3.x). In ogni caso ActiveRecord non è un'opzione. Riformulerò la mia domanda – mb14

+0

Peccato che tu non possa usarla :-(Ma terrò d'occhio questa domanda, forse qualcosa di interessante apparirà :-) –

+1

find_each era in giro dal 2.3.2 secondo l'apidock: http://apidock.com/rails/v2.3.2/ActiveRecord/Batches/ClassMethods/find_each – tokland

-1

Forse si può considerare Ohm, che si basa su Redis NoSQL negozio.

+0

Ho bisogno di qualcosa per MySql – mb14

4
Sequel.extension :pagination 
posts.order(:id).each_page(1000) do |ds| 
    ds.each { |p| puts p } 
end 

È molto molto lento su grandi tavoli!

Diventa chiaro, guardò il corpo del metodo: 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 
2

Questo codice funziona più velocemente di quanto find_in_batches in 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 
+0

Ho usato Sequel.mysql2 –

Problemi correlati