java - Grails, Insertar muchos datos usando withTransaction resulta en OutOfMemoryError
hibernate spring (2)
Ted Naleid escribió una gran entrada en el blog sobre cómo mejorar el rendimiento del lote. Incluyendo aquí como referencia.
Estoy usando Grails 1.1 beta2. Necesito importar una gran cantidad de datos en mi aplicación Grails. Si instanciamos repetidamente una clase de dominio de Grails y luego la guardamos, el rendimiento es inaceptablemente lento. Tomemos, por ejemplo, importar personas de una guía telefónica:
for (each person in legacy phone book) {
// Construct new Grails domain class from legacy phone book person
Person person = new Person(...)
person.save()
}
Esto resulta ser dolorosamente lento. Alguien en la lista de correo de Grails sugiere que se acumulen copias de seguridad en una transacción. Entonces ahora tengo:
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()
Esto funciona más rápido, al menos inicialmente. Cada transacción guarda 500 registros. A medida que pasa el tiempo, las transacciones tardan más y más. Las primeras pocas transacciones tardan unos 5 segundos, luego simplemente se arrastra desde allí. Después de aproximadamente 100 transacciones, cada una toma más de un minuto, lo que una vez más es inaceptable. Peor es que eventualmente Grails eventualmente se quedará sin memoria de pila Java. Puedo aumentar el tamaño del OutOfMemoryError
dinámico de JVM, pero eso solo demora la excepción OutOfMemoryError
.
¿Alguna idea de por qué es esto? Es como si no se hubiera lanzado algún recurso interno. El rendimiento empeora, la memoria se mantiene, y finalmente el sistema se queda sin memoria.
De acuerdo con la documentación de Grails , withTransaction
pasa el cierre al objeto TransactionStatus
de Spring. No pude encontrar nada en TransactionStatus
para cerrar / finalizar la transacción.
Editar: estoy ejecutando esto desde la consola de Grails (consola de Grails)
Editar: Aquí está la excepción de falta de memoria:
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)
Este es un problema común con todas las aplicaciones de hibernación y es causado por el crecimiento de la sesión de hibernación. Supongo que la consola de Grails tiene una sesión de hibernación abierta para ti de forma similar al patrón de ''abrir sesión a la vista'' que sé que usa para solicitudes web normales.
La solución es obtener la sesión actual y borrarla después de cada lote. No estoy seguro de cómo conseguir el frijol de primavera usando la consola, normalmente para controladores o servicios que acaba de declarar como miembros. Luego puede obtener la sesión actual con sessionFactory.getCurrentSession()
. Para borrarlo solo llame a session.clear()
, o si lo que es selectivo use session.evict(Object)
para cada objeto Person
.
para un controlador / servicio:
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()
}
}
}
Espero que esto ayude.