Search code examples
hibernategrailstransactions

Reduce processing time of exiting a Service method back to the Controller method


In my Grails application, I am creating a lot of individual DomainObject during a single call of a save link:

import grails.converters.JSON

class SaveController {
    def saveService

    def save() {
        println (new Date()) + " Enter SaveController.save()."

        Map status = saveService.save(params)
        println (new Date()) + " Finished calling saveService.save()."

        render status as JSON
    }
}

The link calls a service to persist each of its content to the database:

import org.codehaus.groovy.grails.web.servlet.mvc.GrailsParameterMap

class SaveService {
    Map save(GrailsParameterMap params) {
        List<DomainObject> dos = new ArrayList()

        println (new Date()) + " Iterate list and create DomainObject per element."
        params.list.each { l ->
            dos.push(new DomainObject(l))
        }

        println (new Date()) + " Started saving all DomainObjects."
        dos.each { d ->
            d.save()
        }

        println (new Date()) + " Finished saving all ${dos.size()} DomainObjects."

        return [done: true]
    }
}

The problem is it chokes on a certain part of the process, evident to the prints I have placed:

Fri Jun 01 21:47:55 SGT 2018 Enter SaveController.save().
Fri Jun 01 21:47:55 SGT 2018 Iterate list and create DomainObject per element.
Fri Jun 01 21:47:55 SGT 2018 Started saving all DomainObject.
Fri Jun 01 21:48:13 SGT 2018 Finished saving all 4316 DomainObject.
// Waits from some minutes before reaching the next println.
Fri Jun 01 21:52:02 SGT 2018 Finished calling saveService.save().

My guess is that it takes it time persisting/committing each individual .save() call to the database. My question is, is there any way that I can modify the code so that it will commit all of the elements of List<DomainObject> dos once as a whole, thus eliminating the long time needed before exiting saveService.save().

I had tried changing d.save() into d.save(flush: true) but the speed remains the same. I am looking for maybe a groovy Closure that will automatically commit the transactions inside it when it's done, something like this (I don't know if there's an actual Closure like this):

commitOnceDone { session -> 
    dos.each { d ->
        d.save()
    }
}
// Once reached here, will fire a single COMMIT; that will affect
// all transactions that happened inside the Closure

Solution

  • You need to batch the inserts. You need two things to do that :

    For really simple usage / testing, you can use this in application.groovy :

    grails.gorm.default.mapping = {
        'id'(generator: 'increment') // The application instance will generate the ID, so it cannot be clustered 
    }
    

    Fri Jun 01 21:48:13 SGT 2018 Finished saving all 4316 DomainObject.

    Note that at the time you are printing this, nothing is saved in the DB since you haven't flushed the Hibernate session yet. To batch, do not put 'flush: true' in the .save() call so Hibernate will batch the inserts if you setup your application to batch.

    There are other optimizations possible, but this one should be the biggest one.