GPerform: uma aplicação de exemplo com PERFORMANCE na alma

Venho pensando num tema legal para escrever um post. Aí resolvi dar uma atualizada, ler um pouco, ver umas apresentações, ..... E uma coisa que tem me interessado muito ultimamente é o assunto performance. Como uso muito Grails, então o assunto seria Grails e Performance. Então vamos lá.

Depois de algumas leituras, acho que juntei diversas dicas, plugins, e técnicas que vão fazer uma aplicação grails voar em alta velocidade. Mas não queria apenas ficar na leitura, pois sabemos bem que a teoria é uma coisa, na prática.....

Então botei a mão na massa e resolvi criar uma nova aplicação que chamei de GPerform (Grails Performance). Criei uma aplicação (na verdade está sendo criada ainda) que é uma espécie de Twitter + Instagram (em menor escala, naturalmente). Você manda uma foto, com comentário. Você pode seguir pessoas, e pessoas podem te seguir. Você pode avaliar fotos como boa ou ruim. Tudo é público.

A aplicação é 0.1, ok? Mas está disponível no GitHub. Seja bem vindo para contribuir se quiser (estou dando meus primeiros passos com o Git, então paciência comigo....).

A primeira coisa a fazer foi criar o modelo de dados, e aí já começam as dicas para uma melhor performance da sua aplicação: Não usar Collections para as relações hasMany. E mais que isso: acabar com as relações diretas entre duas classes para os casos many-to-many e one-to-many. Pois é, parece estranho.  Mas tem fundamento.

Para esclarecer o fundamento, vou dar um pequeno exemplo. Imagina:

class ContaCorrente{
    
    String numero
    String agencia
    
    static hasMany = [transacoes: Transacao]
}


class Transacao{
    
    BigDecimal valor
    
    static belongsTo = [conta: ContaCorrente]
}


Imagine que você queira usar as facildades que o Grails dá para gerenciar esta relação one-to-many. Ou seja, você vai querer usar o método conta.addToTransacoes(t) que está disponível para você, claro.

Mas imagine quantas transações tem numa conta corrente al longo de um ano. Se você tiver 30 transações por mês, no ano serão 360. Ou seja, no final do ano, quando o seu sistema for adicionar mais uma transação na conta do usuário, ele irá buscar as 360 transações para montar a collection "conta.transacoes" e só depois irá adicionar a nova transação nesta collection, para aí gravar esta alteração na collection no banco de dados. Se inicialmente você queria fazer apenas um insert, você acaba fazendo um select que retorna 360 itens pra memória (que você não vai usar nenhum deles), um insert da nova transação, e um update da ContaCorrente (pois o GORM atualiza o campo version deste objeto pai).

É ou não é coisa demais, e é muito provável que a performance da aplicação será seriamente afetada?

Para resolver isso, Burt Beckwith sugere, em uma apresentação na infoQ, que não se use a relação one-to-many com o hasMany. Que se faça apenas o outro lado da relação, que é o many-to-one. Qual é a perda disso? Você não vai ter o método addToTransacoes(). Mas isso é fácil de resolver. E também não vai ter um atributo "transacoes" que te retorna todas as transacoes da sua conta (você provavelmente não iria usar nunca - nem deveria). (veja um post interessante sobre isso) Por exemplo:

class ContaCorrente{    

    String numero
    String agencia
    
    def buscaTransacoesRecentes(int qtde){
        Transacao.executeQuery('from Transacao t where t.conta = :c order by t.dateCreated desc', [c:this], [max:qtde])
    }

    def adicionaTransacao(BigDecimal valor){
        new Transacao(valor:valor, conta:this).save()
    }

}


class Transacao{
    
    BigDecimal valor
    Date dateCreated

    ContaCorrente conta

}

Pronto. Simples assim. Você agora conseguiu ter apenas o seu único insert que você queria. Sem selects desnecessários, e sem update desnecessário (não estou contando com o select da ContaCorrente, mas tbem não contei com ele ali em cima). Veja que eu criei métodos na classe ContaCorrente para adicionar uma nova transação e para buscar as mais recentes.

Acho que tá bom por hoje né? Terei que escrever outros posts para contar tudo. Tem ainda o uso de diversos plugins, como por exemplo: springcache, spring-events, resources, cache-headers, cached-resources, zipped-resources, e por aí vai. Já já eu volto.

4 comentários:

Everton Sales 16 de setembro de 2011 17:42

Olá,

Interessante a abordagem, mas não teria que criar um método para excluir todas as transações ao excluir a conta ?

e eu colocaria pra ficar mais limpo os métodos "buscaTransacoesRecentes" e "adicionaTransacao" dentro da classe transacao.

Valeu, grandes dicas. Obrigado.

Everton Sales

Felipe Nascimento 19 de setembro de 2011 11:53

Oi Everton,

obrigado pela mensagem. Certamente teria que ter um método para excluir tudo sim, no caso de excluir a conta.

Coloquei os métodos "buscaTransacoesRecentes" e "adicionaTransacao" na classe ContaCorrente pois quando usamos o GORM normal, os métodos de conta.addToTransacoes() e conta.transacoes fica na classe Pai (da relação one-to-many). Então coloquei ali também só para manter o mais próximo possível da abordagem do GORM. Mas, neste caso vc poderia fazer da forma como vc falou também. Não vejo problema algum nisso. Abcs

Pedro 3 de novembro de 2011 22:21

Olá Felipe.

Parabéns pelo Manubia.
Estou no aguardo de mais dicas.
To precisando melhorar desempenho da minha aplicação.

Valeu

Felipe Nascimento 8 de novembro de 2011 12:20

Obrigado Pedro.
Grande abraço.