2012-01-13 1 views
19

Je suis à la recherche d'un ORM Ruby pour remplacer ActiveRecord. J'ai regardé Sequel et DataMapper. Ils ont l'air plutôt bien mais aucun d'entre eux ne semble faire la base: ne pas charger tout en mémoire quand vous n'en avez pas besoin.Y a-t-il des ORM Ruby qui utilisent des curseurs ou des récupérations intelligentes?

Je veux dire que je l'ai essayé ce qui suit (ou équivalent) sur ActiveRecord et Sequel sur la table avec beaucoup de lignes:

posts.each { |p| puts p } 

Les deux deviennent fous sur la mémoire. Ils semblent charger tout en mémoire plutôt que d'aller chercher des choses quand c'est nécessaire. J'ai utilisé le find_in_batches ActiveRecord, mais ce n'est pas une solution acceptable:

  1. ActiveRecord n'est pas une solution acceptable parce que nous avions trop de problèmes avec elle.
  2. Pourquoi mon code doit-il être informé d'un mécanisme de pagination? Je suis heureux de configurer quelque part la taille de la page mais c'est tout. Avec find_in_batches vous devez faire quelque chose comme:

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

Mais cela devrait être transparent.

Alors, y a-t-il quelque part un ORM Ruby fiable qui effectue l'extraction correctement?


Mise à jour:

Comme Sergio mentionné, dans Rails 3, vous pouvez utiliser find_each qui exactement ce que je veux. Cependant, comme ActiveRecord n'est pas une option, sauf si quelqu'un peut vraiment me convaincre de l'utiliser, les questions sont les suivantes:

  1. Quels ORM supportent l'équivalent de find_each?
  2. Comment le faire?
  3. Pourquoi avons-nous besoin d'un find_each, alors que find devrait le faire, n'est-ce pas?

Répondre

43

Le Dataset#each de Sequel produit des lignes individuelles à la fois, mais la plupart des pilotes de base de données chargeront tout le résultat en mémoire en premier.

Si vous utilisez l'adaptateur Postgres Sequel, vous pouvez choisir d'utiliser les curseurs réels:

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

Ce va chercher 1000 lignes à la fois par défaut, mais vous pouvez utiliser une option pour spécifier le nombre de lignes à grab par curseur ira chercher

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

Si vous n'utilisez l'adaptateur Postgres Sequel, vous pouvez utiliser l'extension de pagination Sequel:

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

Cependant, comme find_in_batches/find_each d'ActiveRecord, cela permet de séparer les requêtes. Vous devez donc faire attention à la modification simultanée de l'ensemble de données que vous extrayez.

La raison pour laquelle ce n'est pas la valeur par défaut dans Sequel est probablement la même raison pour laquelle ce n'est pas la valeur par défaut dans ActiveRecord, qui est que ce n'est pas un bon défaut dans le cas général. Seules les requêtes avec de grands ensembles de résultats doivent vraiment s'en soucier, et la plupart des requêtes ne renvoient pas de grands ensembles de résultats.

Au moins avec l'adaptateur Postgres support du curseur, il est assez facile de le faire par défaut pour votre modèle:

Post.dataset = Post.dataset.use_cursor 

Pour l'extension de pagination, vous ne pouvez pas vraiment faire cela, mais vous pouvez envelopper dans une méthode qui le rend presque transparent.

+0

Ça marche aussi pour MySql ou est-ce seulement avec Postgres? – mb14

+0

Le contenu de use_cursor est postgres-only. Je ne suis pas sûr que MySQL supporte les curseurs pour retourner les résultats. La documentation du curseur MySQL indique que "MySQL supporte les curseurs dans les programmes stockés", voir http://dev.mysql.com/doc/refman/5.6/en/cursors.html. –

+0

PHP utilise massivement db_fetch, donc je suppose que MySQL supporte les curseurs. Cependant peut-être pas dans le pilote ruby ​​par défaut – mb14

3

ActiveRecord a en fait une presque transparente batch mode:

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

Merci (+1). C'est dans Rail 3, (nous utilisons les rails 2.3.x). Anyway ActiveRecord n'est pas une option. Je vais reformuler ma question – mb14

+0

Dommage que tu ne puisses pas l'utiliser :-(Mais je garderai un oeil sur cette question, peut-être que quelque chose d'intéressant va apparaître :-) –

+1

find_each était là depuis 2.3.2 selon apidock: http://apidock.com/rails/v2.3.2/ActiveRecord/Batches/ClassMethods/find_each – tokland

-1

Peut-être que vous pouvez envisager Ohm, qui est basé sur Redis magasin NoSQL.

+0

J'ai besoin de quelque chose pour MySql – mb14

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

Il est très très lent sur les grandes tables!

Il devient clair, regarda le corps de la méthode: 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

Ce code fonctionne plus vite que find_in_batches 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

J'ai utilisé Sequel.mysql2 –

Questions connexes