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.
- 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
- 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
- Í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