style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

Inserción de datos masiva problemas de lentitud con la sesión de GORM

Hola que tal pues me encuentro como dice el titulo atorado en un problema de lentitud en la carga de datos a mi aplicación Grails revise esta liga y es muy parecido http://www.javamexico.org/foros/java_enterprise/jpa_masivo les cuento lo que sucede

Tengo una clase de dominio la cual llamaremos Contenedor y una segunda clase llamada Objeto(los nombres de las clases no son relevantes) las cuales tienen mas o menos la siguiente estructura:

class Contenedor{
     def nombre
     static hasMany = [objetos:Objeto]
}

class Objeto{
    def fecha
    def mensaje
    static belongsTo = [contenedor:Contenedor]
}

después en un servicio tengo un ciclo y en ese ciclo realizo la carga de registros mas o menos de la siguiente manera:

def contenedor = Contenedor.get(contenedorID)
archivoAprocesar.eachLine{
     def row = //obtenemos un mapa de la fila del archivo a validar
     def objeto = validaFila(row) //realiza varias validaciones y devuelve un objeto valido
     contenedor.addToObjetos(objeto)
}
contenedor.save()

como podemos observar en el código anterior únicamente hacemos la lectura de un archivo mapeamos cada fila a un row, validamos y devolvemos un objeto valido con ó sin errores pero siempre una instancia valida de Objeto finalmente se agrega ese objeto al contenedor y cuando termina de validar todas las filas se guarda el contenedor para así hacer el commit final.

El problema con este código es que se tiene un performance muy malo pues al inicio lo hace rápidamente perdiendo velocidad cuando se incrementa el numero de registros agregados, esto es lógico pues se cargan en memoria todos los objetos creados y no se liberan sino hasta que se hace el commit final.

Un compañero busco una adaptación la cual consiste en vaciar los objetos de la cache de la sesión cada cierto numero de objetos y sí funciona pero tiene un inconveniente veamos como quedaria ya con la adaptación:

def contenedor = Contenedor.get(contenedorID)
def indice = 0
archivoAprocesar.eachLine{
     def row = //obtenemos un mapa de la fila del archivo a validar
     def objeto = validaFila(row) //realiza varias validaciones y devuelve un objeto valido
     contenedor.addToObjetos(objeto)
     indice ++
     if(indice %200 == 0) limpiaSession(contenedor)
}
contenedor.save()

def limpiaSession(contenedor){      
        contenedor.save() // esto para que persista los objetos a la base antes de limpiar la cache
        def session = sessionFactory.currentSession
        session.flush()
        session.clear()
    }

con esto ya tenemos parte de la solución pues baja considerablemente los tiempos entonces lanzamos el proceso y vemos que efectivamente realiza las inserciones de manera rápida y cada 200 objetos se limpia la sesión previamente se hace el save al contenedor para que los persista, hasta aquí feliz, sin embargo ahora el problema de la lentitud pasa cada vez que va a limpiar la sesión por poner tiempos digamos que los primeros 200 registros va y ejecuta el método limpiaSession y se tarda 1 segundo luego a los 400 ejecuta de nuevo el método y se tarda 1 segundo pero ya a los 1000 ejecuta el método y se tarda 2 segundos a los 2000 va ejecuta el método y se tarda 3 segundos y así se la lleva.

Entonces eliminamos el tiempo que se tarda en meter los objetos a la BD haciendo constante lo que tarda cada 200 objetos, pero el tiempo que tarda en limpiar la sesión se incrementa ¿que puede estar pasando? ¿como resolverlo?

Saludos y gracias.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

inserción masiva

usar un ORM para inserción masiva de datos es mala idea, como ya te diste cuenta. Usa mejor JDBC directo, o alguna interfaz encima de eso como el JdbcTemplate de Spring o el groovy.sql.Sql.

Imagen de Shadonwk

Sí entiendo, con respecto a

Sí entiendo, con respecto a usar groovy.sql.Sql se respetan las reglas de transaccionalidad al hacerlo desde un servicio Grails o tendría que implementarla yo mismo?

Imagen de ezamudio

withTransaction

Creo que tiene un método withTransaction. Lo importante es que al crear tu objeto Sql lo hagas con el dataSource de la aplicación y no con el driver/user/pass (eso es para scripting)

Imagen de Shadonwk

efectivamente leyendo

efectivamente leyendo encontré que si se puede incluso simplemente con estar en el servicio de grails ya lo hace transaccional, lo que ahora estoy buscando es como relacionar los objetos como se ve en el ejemplo de arriba, el contenedor tiene una relación hacia objetos entonces al crear los objetos que son los que se insertan de manera masiva como hago la relación en el contenedor, también tendría que hacerla manual?

para crear la conexión encontre esto:

versión 1

    def dataSource              // For inject the datsource
    def doSomething() {
       def sql = new Sql(dataSource) // You can create the Sql object here
    }

versión 2

   def sessionFactory
   def someMethod() {
      Sql sql = new Sql(sessionFactory.currentSession.connection())
  }

no he probado todavía pero supongo que funcionan, cual de las dos es mejor ?

Imagen de ezamudio

dataSource

La que usa el dataSource

Imagen de benek

Otro problema de limpiar la

Otro problema de limpiar la sesión entera es que no solamente se lleva la caché de los que acabas de insertar, sino la de todos, y puede ser conflictivo en situaciones en las que tienes entidades cargadas.

Existe una manera de limpiar solamente la caché de segundo nivel para cierta clase de dominio, haciendo evict() sobre la session factory y pasando como parámetro la clase de dominio, puedes desalojar una sola instancia o todas las instancias de esa clase de dominio. De hecho, en su momento le comenté a Verde que lo implementara ahí en el proyecto en el que estás (si es que es para ese), probablemente lo hizo y puedas tomar ese ejemplo.

Hay otro issue que también debes considerar para hacer tunning en estas situaciones, Grails cuenta con una caché para validaciones en los objetos (que no tiene nada que ver con la caché de Hibernate), la cuál también va dejando objetos en la heap, por lo que a la par de limpiar la caché de Hibernate puedes limpiar también este mapa de objetos. El mapa lo obtienes desde org.codehaus.groovy.grails.plugins.DomainClassGrailsPlugin.PROPERTY_INSTANCE y suponiendo que lo pases a una variable harías .get().clear() sobre el mapa y listo.

Te dejo un artículo de Burt Beckwith sobre este leak.

Saludos.

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">