Desempenho no Grails ao salvar objetos em lote

Updated . Posted . Visible to the public. Draft.

Em um dos meus estudos em Grails me deparei com uma certa lentidão ao salvar objetos no banco, mais especificamente ao ler um arquivo csv e salvar os dados no banco.

Segue um exemplo: Temos uma lista de pessoas que realizaram uma ligação para outra pessoa. Ou seja, dois objetos: Pessoa e Ligacao.

class Pessoa {
    String nome
}
class Ligacao {
    Pessoa pessoa1
    Pessoa pessoa2
}

No meu caso eu tinha uma lista de dados, mas para simplificar vamos tratar com dados aleatórios. Dentro de um controlador (eu escolhi o PessoaController), coloquei a seguinte action:

	def perf = {
		Ligacao.list().each { it.delete() }
		Pessoa.list().each { it.delete() }

		def d1 = perform(100)
		def d2 = perform(1000)

		render  ("d1: " + d1 + "ms<br/>d2: " + d2 + "ms")
	}

	private long perform (int times) {
		def pessoas = ['JOAO', 'MARIA', 'FRANCISCA', 'GILBERTO', 'GONADOTROFINA', 'MATEUS', 'GIOVANA']
		def r = new Random()
		def d = new Date().time
		for (def i = 0; i < times; i++) {
			def nomePessoa = pessoas[r.nextInt(pessoas.size())]
			def pessoa = Pessoa.findByNome(nomePessoa)
			if (!pessoa) {
				pessoa = new Pessoa(nome: nomePessoa)
				pessoa.save(flush: true)
			}

			def nomePessoa2 = pessoas[r.nextInt(pessoas.size())]
			def pessoa2 = Pessoa.findByNome(nomePessoa2)
			if (!pessoa2) {
				pessoa2 = new Pessoa(nome: nomePessoa2)
				pessoa2.save(flush: true)
			}

			def ligacao = new Ligacao(pessoa1: pessoa, pessoa2: pessoa2)
			ligacao.save(flush:true)
		}
		return new Date().time - d
	}

Na minha máquina obtive este resultado:
d1: 6802ms
d2: 66318ms

Ou seja, para 100 registros, demora-se quase sete segundos para salvar os dados.

  1. Remover flush:true

É um costume meu colocar flush:true ao salvar objetos. Nunca se sabe quando os objetos vão ser salvos sem essa instrução, então eu sempre coloco. Mas para operações em lote isso significa um peso para o banco de dados, já que são 100 (ou 1000) conexões ao banco para enviar apenas um comando.

Retirando o flush:true temos os seguintes números:
d1: 6472ms
d2: 64930ms

  1. Ids nativos

Por mais fácil que seja deixar o banco cuidar dos índices do objeto, não é o mais performático (se é que esta palavra existe. Enfim.) Mesmo que as operações estejam dentro de uma transação o Hibernate precisa salvar o objeto no banco para obter um novo id. Se colocarmos o Hibernate para gerenciar os ids, podemos evitar que estas inserções sejam feitas uma a uma e enviá-las em lote.

Para mudar este comportamento, insira este código nas classes de domínio:

    static mapping = {
        id generator: "hilo", params:[table:'user_id_gen',column:'next_value']
    }

resultado:
d1: 7097ms
d2: 70619ms

  1. Índices

Índices são como um guia rápido para o banco de dados encontrar uma linha. Colocar um índice em uma coluna que é muito acessada pode acelerar (e muito) o código.

	static mapping = {
		id generator: "hilo", params:[table:'user_id_gen',column:'next_value']
		nome index: 'pessoa_nome_idx'
	}

Resultado:
d1: 6925ms
d2: 66790ms

Daniel Lobo
Last edit
Posted by Daniel Lobo to ZeroGlosa (2012-10-09 21:06)