2009-01-10 5 views
12

J'utilise Grails 1.1 beta2. J'ai besoin d'importer une grande quantité de données dans mon application Grails. Si j'instancie plusieurs fois une classe de domaine grails et que je l'enregistre ensuite, la performance est trop lente. Prenez par exemple l'importation de personnes à partir d'un annuaire téléphonique:Grails, L'insertion de beaucoup de données en utilisant withTransaction aboutit à OutOfMemoryError

for (each person in legacy phone book) { 
    // Construct new Grails domain class from legacy phone book person 
    Person person = new Person(...) 
    person.save() 
} 

Cela s'avère être extrêmement lent. Quelqu'un sur la liste de diffusion Grails suggère de sauvegarder les sauvegardes dans une transaction. Donc maintenant j'ai:

List batch = new ArrayList() 
for (each person in legacy phone book) { 
    // Construct new Grails domain class from legacy phone book person 
    Person person = new Person(...) 
    batch.add(person) 
    if (batch.size() > 500) { 
     Person.withTransaction { 
      for (Person p: batch) 
       p.save() 
      batch.clear() 
     } 
    } 
} 
// Save any remaining 
for (Person p: batch) 
    p.save() 

Ce travail doit être plus rapide, au moins initialement. Chaque transaction enregistre 500 enregistrements. Au fil du temps, les transactions prennent plus de temps et plus. Les premières transactions prennent environ 5 secondes, puis elles viennent juste de là. Après environ 100 transactions, chacune prend plus d'une minute, ce qui est encore une fois inacceptable. Le pire est que finalement Grails finira par manquer de mémoire de tas Java. Je peux augmenter la taille du tas JVM, mais cela retarde juste l'exception OutOfMemoryError.

Des idées pourquoi c'est? C'est comme si une ressource interne n'était pas publiée. Les performances s'aggravent, la mémoire est maintenue, puis le système manque de mémoire.

Selon le Grails documentation, withTransaction passe la fermeture à l'objet TransactionStatus de Spring. Je n'ai rien trouvé dans TransactionStatus pour fermer/terminer la transaction.

Edit: Je suis en cela de la console (grails console)

de Grails Edit: Voici le hors d'exception de la mémoire:

Exception thrown: Java heap space 

java.lang.OutOfMemoryError: Java heap space 
    at org.hibernate.util.IdentityMap.entryArray(IdentityMap.java:194) 
    at org.hibernate.util.IdentityMap.concurrentEntries(IdentityMap.java:59) 
    at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:113) 
    at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:65) 
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26) 
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000) 
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338) 
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106) 
    at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:655) 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:732) 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:701) 
    at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140) 
+0

Quel est le contexte dans lequel cela s'exécute? Un travail à quartz? Un contrôleur? Quand nous l'avons fait dans le passé en utilisant un contrôleur nous a permis de définir une boucle qui peut lier la taille du lot, avec la taille de la transaction suivante dans le Service en conséquence –

Répondre

12

Ce problème est commun avec tous Mise en veille prolongée applications et il est causé par la croissance de la session d'hibernation. Je suppose que la console grails tient une session d'hibernation ouverte pour vous de la même manière que le modèle 'open session in view' que je sais utiliser pour les requêtes web normales.

La solution est d'obtenir la session en cours et de l'effacer après chaque lot. Je ne sais pas comment vous obtenez le haricot de printemps en utilisant la console, normalement pour les contrôleurs ou les services que vous venez de déclarer comme membres. Ensuite, vous pouvez obtenir la session en cours avec sessionFactory.getCurrentSession(). Afin de l'effacer il suffit d'appeler session.clear(), ou si vous souhaitez être sélectif, utilisez session.evict(Object) pour chaque objet Person.

pour un contrôleur/service:

class FooController { 
    def sessionFactory 

    def doStuff = { 
     List batch = new ArrayList() 
     for (each person in legacy phone book) { 
      // Construct new Grails domain class from legacy phone book person 
      Person person = new Person(...) 
      batch.add(person) 
      if (batch.size() > 500) { 
       Person.withTransaction { 
        for (Person p: batch) 
         p.save() 
        batch.clear() 
       } 
       // clear session here. 
       sessionFactory.getCurrentSession().clear(); 
      } 
     } 
     // Save any remaining 
     for (Person p: batch) 
      p.save() 
     } 
    } 
} 

Hope this helps.

+0

Je améliorerais ce code en utilisant session.clear() chaque Nième itération de la boucle, pas chacun. –

+0

complètement d'accord ... en regardant ce code maintenant, je ne suis pas sûr si cela fonctionne même. –

15

Ted Naleid a écrit un great blog entry sur l'amélioration des performances du lot. Y compris ici comme référence.

Questions connexes