Consumo de memória em servidor Tomcat, utilizando VRaptor

24 respostas
M

Bom dia.
Gostaria de perguntar ao pessoal que trabalha com VRaptor sobre a experiência deles na utilização do VRaptor, seja com Tomcat ou outro AS qualquer, na questão de performance e consumo de memória.
Estamos utilizando o VRaptor para implementação de sistemas aqui na universidade onde trabalho.
Utilizamos Hibernate para a persistência e criteria para as consultas, retornando informações em JSON para a view, desenvolvida em Extjs.
Aliás, percebemos que a utilização do hibernate para consultas faz com que muita informação seja trazida do banco de dados, mesmo sem ter necessidade, para popular os objetos que representam nosso modelo.
Acontece que o servidor de aplicação está consumindo bastante memória, com poucos usuários logados.
Fizemos alguns testes e ajustes finos para tentar melhorar.
Em um destes testes de carga, instanciamos 300 sessões para uma determinada página que abre uma sessão com o banco de dados. O servidor ocupou cerca de 96% da memória “old gen”, fazendo com que os GCs sejam executados em intervalos curtos de tempo, deixando as aplicações lentas. Depois deste teste, matamos todas as sessões criadas, e mesmo assim, a memória não foi liberada e o servidor continuou lento.
Algumas semanas atrás conversamos com uma pessoa que se dizia expert em servidor de aplicação. Ele disse que o problema todo estava no framework que utilizamos, o qual, segundo ele, “não implementa garbage collection”. Achamos estranha essa colocação dele.
Enfim, gostaria de algumas dicas de quem já teve problema semelhante, para tentar resolver este problema.

Obrigado.

24 Respostas

Rafael_Guerreiro

Uma coisa que eu percebi é que o Guice como container é bem rápido e leve. Me tornei adepto à ele. Já que eu não uso a maioria das funções do Spring. Mas algumas do Guice.

Geralmente o que consome muita memória são as classes @SessionScoped e @ApplicationScoped. Veja se você não está populando demais essas classes. Ou se você tem classe @SessionScoped desnecessária, sendo que poderiam ser @ApplicationScoped. Por exemplo o @Component que cria a SessionFactory do Hibernate, ele pode ser @ApplicationScoped e usar um connectionPool (c3p0, por exemplo).

Minhas aplicações são bem leves e geralmente bem rápidas e fazem bastante coisa. Mas eu sempre encontro algo que eu posso melhorar. :smiley:

Quanto ao Hibernate, você pode usar o Lazy para carregar as suas entidades sem precisar puxar todas as outras tabelas relacionadas à ela. E quando você precisar das outras tabelas, você faz um Hibernate.initialize(obj.getOutraClasse());

Lucas_Cavalcanti

Bom, como o Rafael falou, se vc não usa nada específico do Spring, troque para o Guice.

Evitar relacionamentos EAGER no hibernate, quando eles não fazem sentido, também ajuda

M

Então, na verdade os relacionamentos não estão Eager, mas algumas consultas que são feitas retornando um objeto completo, por exemplo, acabam retornando os objetos relacionados também, por causa do flexjson, que acaba percorrendo os atributos que representam chave estrangeira. Vocês acham q isso é um ponto que pode gerar grande consumo de memória? E sobre essa questão da memória continuar sendo ocupada, mesmo após as sessões serem excluídas? Isso é normal mesmo?

Rafael_Guerreiro

Talvez as sessões não estejam sendo destuídas.
Geralmente eu uso o httpSession.invalidate(); implicitamente e ele destrói a sessão.

Então nesse caso do Hibernate, você vai precisar mandar ele não trazer aqueles relacionamentos na consulta. Vai deixar eles null mesmo.

Tente colocar o mesmo SessionFactory para todos os usuários. Se seu seu componente de SessionFactory estiver anotado com @SessionScoped, anote o seu componente de SessionFactory com @ApplicationScoped.

Isso dá uma aliviada no uso de memória e na velocidade do sistema.

M

Essas sessões que eu disse que foram destruídas, foram pelo gerenciador do Tomcat (matei as sessões!), e na lista das sessões abertas, não sobrou nenhuma. Nesse caso elas foram destruídas mesmo né, não precisaria dar o comando que vc passou, não é?

Sobre a SessionFactory, só pra esclarecer, nós não usamos um pool de conexões, pois cada usuário tem seu usuário e senha para conectar no banco de dados.

Nossa classe construtora de sessões está assim:

@Component
@RequestScoped
public class CriadorDeSessionFactory implements ComponentFactory

public SessionFactory getInstance()

Vamos tentar alterar para @Application para ver se melhora :slight_smile:

FernandoFranzini

Me desculpe, mas essa frase ficou totalmente sem sentido…
Problemas com memoria é classificado com 2 tipos

1. A solução esta gastando memória desenfreadamente
O que eu mais vejo nas consultorias e fóruns são os desenvolvedores despreparados, que mesmo bem tensionados acabam escrevendo código que resulte em gastos de memória totalmente desenfreados, sem nenhum critério, preocupação ou um padrão arquitetural previamente estabelecido.

2. A solução já esta otimizada, mas ainda não é suficiente. O que fazer?
Caso a solução já esta 100% otimizada, entenda que a única coisa que pode ser feito é aumento da memória. Ou seja, se a solução esta gastando o mínimo possível e ainda assim esta precisando de mais memória, a unica resposta lógica e cabível é que o copo de 300 ml não é suficiente para executar essa determinada solução. Isso não é o fim do mundo, muito pelo contrario, é algo totalmente corriqueira e normal. Situação muito comum em aplicativos Java para Web no qual o número de usuários simultâneos é gradativamente crescente no tempo de vida de existência da solução.

Um framework ruim pode ocasionar problemas de memoria, mas acredito que não seje o caso do VRaptor…eu não usei ele!

Para detalhes, leia - http://fernandofranzini.wordpress.com/2011/12/13/a-famosa-falta-de-memoria-java-lang-outofmemoryerror/

Lucas_Cavalcanti

claro que tá demorando, vc não pode criar sessionFactory pra cada request!!!

tem que criar só um pra aplicação inteira…

se cada usuário do sistema vai ter um usuário diferente no banco (tem certeza que vc precisa disso?) tente sobrescrever o ConnectionProvider do hibernate para usar os dados do usuário logado…

M

Ok, já colocamos o Guice em uma aplicação, para testar.
Também vamos tentar deixar a sessionfactory em escopo de aplicação.
Volto a postar o resultado! :slight_smile:
Obrigado.

P

Que eu saiba quem implementa o GC é a JVM e não o framework então manda esse “especialista” ir passear !!!

Mas me responda uma coisa, pra cada usuario vc vai la no banco e cria uma conexão com o usuario/senha por ele informado ? Se sim, consequentente vc ta criando um SessionFactory do Hibernate por usuario e isso é a forma mais errada de usar o hibernate, o certo é vc ter um usuario de banco apenas e criar um SessionFactory para a aplicação inteira, isso não é uma aplicação VB/Deplhi no antigo estilo client/server. Vc leu a apostila FJ-28 ? Lá tem as boas praticas de desenvolvimento.

M

O problema é que temos um esquema de auditoria legado que funciona a nível de banco de dados. Ele pega informação do usuário que está conectado no momento que a operação é feita. Se conectarmos com um usuário genérico, vai ferrar com essa auditoria!

Talvez o melhor seja alterar a forma como implementamos essa auditoria, para se ajustar melhor aos requisitos das aplicações web com hibernate, já que esse tipo de auditoria foi implementada para sistemas client/server.

Rafael_Guerreiro

Se você realmente precisa de vários usuários diferentes no banco. Deixa seu ComponentFactory de SessionFactory anotado com @SessionScoped.

Já vai fazer uma grande diferença.

Mas o ideal mesmo é que seja genérico…

G

Auditoria = Hibernate Envers.

Agora… esse assunto de VRaptor implementar GC… eu fiquei rindo aqui por uns 5 minutos. Realmente esse cara precisa fazer um curso básico de Java novamente.

De qualquer forma, se você precisa mesmo usar uma conexão por usuário, sua SessionFactory deveria estar em escopo SessionScope, já que você terá uma conexão por usuário. Fazer isso per-request realmente é um custo muito alto, pois o tempo de subir uma SessionFactory é muito elevado. Mesmo assim, evite usar essa história de uma conexão por cliente. Há técnicas bem mais elegantes de fazer auditoria. Dê uma lida no envers.

boneazul

É sempre o coitado do framework , do sgbd , e a coitada da jvm ou do java que leva a culpa de aplicações mal construídas, quando o assunto é performance e consumo de recurso !!! Canso e tenho pavor de ouvir esse tipo de coisa ! Eu até penso : "É com certeza é o framework o sgbd e afins que não prestam , faz 30 anos que estão no mercado e voce com a sua aplicação que acabou de sair do forno que estão certos "

Ja cheguei a ver uma aplicação JSF em que todos componentes eram alocados em escopo de sessão e o server caia direto dando adivinha o que ?? Falta de memória. Mas é logíco que vai cair e faltar memória , 300 usuarios fazendo requisicao toda hora e os componente lá ficando alocado na sessão e enchendo mais e mais nao tem server e memória que de jeito .

Ai vamos pelo mais facil : “Java é lento” , “Tem que fazer tuning do appserver” , “A rede não é boa” , “O banco é o gargalo” , a ultima coisa que se houve : " Deve ter algo errado na aplicação preciso dar uma olhada ". Antes de partir pra qualquer tuning de infra estrutura no minimo o profissional tem que garantir que a aplicação está nos seus 100% de performance e nao sai mais 1 linha de código pra melhorar .

O cara constroi sem ter a noção básica de java ou de ciclo de vida ou de qualquer coisa sobre a tecnologia em questão e fala uma asneiras dessas que o framework nao implementa GC .

Lógico se acha que ele vai falar que a aplicação dele que ta mal feita ou mal projetada ?? Jamais.

f4314n0

e pra usar o guice como container DI e continuar com spring no controle de transações é tranquilo? ou não seria aconselhado?
hoje ja uso o spring mas ainda nao coloquei a aplicação em produção. com base nas discussões que tenho visto acredito que a hora é agora pra trocar o DI… o impacto é muito complicado?

valeu povo!

G

Agora que parei de rir da piada do “implementar GC”, vamos lá…

Não conheço a tua app, então tudo que vou sugerir é mero “achômetro”, e cito isso baseado em experiências passadas. Aliás, falando em experiências em performance, uma boa sugestão é o www.javaperformance.com.br, do Jonas Abreu.

O Spring é muito bom, porém ele foi o primeiro DI existente, e claro, carregar esse legado tudo tem um cusco. Guice é bem mais leve para os usos do dia a dia. Fiz uns testes usando o Apache AB e tem uma boa diferença.

Dê uma analisada no modelo do banco de dados. Um exemplo bem recente que te dou é um blog que eu tenho que é escrito em Java. Nele tenho, na barra lateral, um ponto onde exibo os últimos comentários do blog, algo como “Fluano comentou em Performance Java”. Sendo assim pelo padrão do Hibernate era feito um select nos comentários gerando um select * from BlogComment, depois para cada comentário era executado um select * from BlogEntry where id = ?. Para os 7 comentários exibidos eram executados 8 consultas, o famoso N+1. Note que além disso ambas entidades possuem muitos campos, e eu só exibia 3 campos de cada um. O correto seria usar um fetch join, porém há um bug no JPA critéria que não funciona isso, então o que fiz foi carregar somente as propriedades que preciso e todas de uma vez: https://gist.github.com/2387828.

Ainda com gancho no assunto, se tua tabela está muito grande, particione a tabela. Pegando como exemplo uma tabela de cliente que possui 100 campos, defina os mais importantes e mais acessados e crie uma tabela CLIENTE e outra CLIENTE_DETALHE com os dados extras. Isso porque você corre o risco de carregar toda uma tabela para ler apenas três campos, algo como acontecia no exemplo acima.

Evite também fazer muitas transformações para exibir dados na tela. No meu blog, por exemplo, a cada comentário mostro um avatar do usuário com a foto do Gravatar, que é calculado usando um MD5 encima do email. Ao invés de fazer isso em tempo de execução, eu já gravo o comentário com o hash no banco, pois assim evito recalcular o campo toda hora. Calcular esse hash é bem rápido, porém imagine 50 comentários em uma tela com 1mil clientes acessando…

Indíces: isso é uma coisa que ninguém se preocupa, mas é muito importante. Em sistemas que você tem muitos acessos é muito importante achar os melhores indices. Um bom começo é fazer um levantamento de todas as consultas necessárias no sistema e montar indices baseado nelas. Outra coisa muito importante: analise se o banco está usando os tipos corretos, e dependendo do banco um determinado tipo possui performance melhor que outros. Por exemplo, no MySQL definir campos booleans como byte é melhor que int(1). No Oracle, por exemplo, você pode gerar um plano de execução, e com base nos dados mostrados, você terá informações para otimizar bem os índices.

Mandando o Hibernate imprimir os comandos SQL é uma boa opção para você fazer um tunning. O problema acima detectei assim, acessando localmente o site e analisando as queries e pensando sempre em diminuir ao máximo a quantidade de consultas ao carregar uma única página. Outra sugestão é o Hibernate Statistics, onde você habilita este recurso que te mostra o uso da Session, objetos em memória, entidades carregadas, dirty objects, etc.

Outras questãos de Hibernate: configuração.

  1. Habilitar logs detalhados somente em desenv. Em PRD/HML use o logger como WARN. Aliás, qual implementação de logging você usa? Isso pode impactar muito na performance, já que logging gera muito I/O. Aliás, você sabe que os frameworks de logging normalmente fazem log síncrono? Uma opção é configurar o logging para asíncrono (no JBoss basta uma única linha no logging.properties). Outro detalhe: o log4j + commons-logging são péssimos em performance.
  2. Você usa algum cache de segundo nível? Talvez seja importante analisar esta questão caso você tenha muitos acessos a uma determinada consulta. Configurando bem o 2nd level cache para as consultas mais utilizadas você pode ter um bom ganho.

Você usa tomcat para uma aplicação crítica? Repense seus conceitos e use um Application Server de verdade. Não é que eu tenha preconceito com o Tomcat ou Jetty, porém tem certos assuntos que adultos devem resolver. Se você tem algo crítico, é para isso que serve um application server.

Um ponto muito importante: não manipule nada de infra na tua aplicação. Infra é de responsabilidade do Application Server. Deixe que ele gerencie conexões de banco, mais sessions, transações, deixe que ele gerencie o ciclo de vida destes recursos, e sempre que você precisar algo de infra, peça para ele. Além disso todos os Application Servers possuem monitores de uso das aplicações, inclusive de conexões de banco, recursos JMS, servlets/filters, EJBs, e tudo mais.

Por mais que o VRaptor seja fantástico em trabalhar com o modelo, dependendo do que tua aplicação crescer, talvez seja a hora de pensar em um EJB ou CDI para controlar o ciclo de vida dos objetos do modelo. Tenho usado um mix de CDI com EJB em vários projetos meus e não tenho o que reclamar. E o VRaptor faz o lookup destes objetos tranquilamente.

Já no lado da infra, outra coisa a observar é como está configurado as instâncias de tua aplicação. Delegar, por exemplo, 4G para uma aplicação é um tiro no pé. Aliás, é um tiro na cabeça. Lembro que em um treinamento que fiz há dois anos atrás sobre JBoss AS, comentamos sobre um caso peculiar. Alguém alocou 20G de memória para uma aplicação. Tudo funcionava bem, até que os 20G lotada. Aí quando o GC iria passar, a aplicação congelava até o GC passar, e isso levava horas. Não lembro se os valores era bem esses, mas é algo assim. O mais correto é você ter várias pequenas instâncias da aplicação com um load balance.

Obviamente o assunto pode ser melhor discutido, mas o que lembro até agora é isso. Ainda mais que não conheço a tua APP. E como dizem por aí, não existe receita de bolo para otimização. Porém tomando pequenos cuidandos você tem uma aplicação bem próxima ao ideal.

E não pense que é só código ruim que faz a aplicação ter problemas de performance. Muitas vezes é simplesmente configuração. O Hibernate pode sim ter culpa nos problemas de performance, pois se ele estiver mal configurado, será ele o gargalo.

f4314n0

Excelentes colocações garcia. É sempre bom lembrar da aplicação como um todo no tocante à performance. Banco de dados deve estar com a sintonia fina muito nitida. Imprescindível tambem ter os logs em desenvolvimento. Nao vejo como desenvolver algo sem ver o que esta sendo produzido. Saber quando e porque usar 2nd level cache e um pool de conexões descente tambem é primordial.
Nao sabia do detalhe de log4j com commons-logging… Vou pesquisar.
Garcia, voce ja usou o nodejs? Vou fazer uns testes e ver o que ele pode fazer.
Garcia, você comanda garoto. Sua contribuição foi de grande valia para mim.
Obrigado e um abraço.

G

f4314n0:
e pra usar o guice como container DI e continuar com spring no controle de transações é tranquilo? ou não seria aconselhado?
hoje ja uso o spring mas ainda nao coloquei a aplicação em produção. com base nas discussões que tenho visto acredito que a hora é agora pra trocar o DI… o impacto é muito complicado?

O VRaptor faz toda a fachada entre a tua aplicação e os providers. Desta forma basta você remover todos os jars do Spring e colocar os do Guice.

Nunca usei.

Eu lembrei de uma coisa importante, sobre os jars da aplicação. Deixe apenas os que você realmente usa. Conselho: remove tudo e adiciona somente o que você precisa.

f4314n0

bem lembrado garcia. esse lance dos jars sempre da pano pra manga.
sempre tem alguem dizendo de erros que aparecem no projeto e que no fim das contas era um jar a mais ou a menos ou imcompativel.
se eu remover os jars do spring o vraptor vai continuar controlando as transações com o banco via hibernate? a unica configuração que tenho hoje é no web.xml:

<context-param>
    <param-name>br.com.caelum.vraptor.packages</param-name>
    <param-value>br.com.caelum.vraptor.util.hibernate</param-value>
</context-param>

alem dos jars do spring na lib…

obrigado novamente a sua disposição garcia!
é muito bom ver gente que sabe do assunto explanando um pouco do conhecimento.

f4314n0

esqueci de comentar que tambem anoto os metodos do dao que fazem alteração no banco com @Transactional.

G

O que eu comentei não é a questão de jars incompatíveis. Mas sim quantidade de jars que você não utiliza e que está no WEB-INF/lib. Ou seja, que poderiam ser apagados.

G

Não é necessário, o vraptor faz todo o controle transacional.

f4314n0

Nossa. Vou testar isso. O vraptor sobe cada vez mais no meu conceito.
Valeu garcia.

rock

Parabéns Garcia!
Gostei do texto.

O que você recomenda utilizar para o log da aplicação?

G

Eu tinha os links aqui, porém não estou achando. Lembro que é algo relacionado com o commons-logging, não exatamente ao log4j. Porém como o log4j usa o commons-logging, ele herda o mesmo problema.

Em algumas aplicações tenho usado o java.util.logging por baixo do SLF4J, e nos mais atuais tenho usado o Logback (http://logback.qos.ch/), que a proprósito é do criador do LOG4J, o Ceki Gulcu.

Oops, enquanto escrevia o texto, achei: http://articles.qos.ch/classloader.html e http://articles.qos.ch/thinkAgain.html.

Criado 11 de abril de 2012
Ultima resposta 20 de abr. de 2012
Respostas 24
Participantes 9