Desempenho no Grails ao salvar objetos em lote

Posted Over 11 years ago. 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
Almost 11 years ago
Posted by Daniel Lobo to ZeroGlosa (2012-10-09 21:06)