Melhorando o GUJ: Jetty, NIO e load balancing

Por Fabio Kung em 27/06/08

GUJ2

Durante boa parte da vida do GUJ.com.br, na sua segunda versão (screeshot acima), o site sofreu diversas quedas e passou por muitos períodos de lentidão, mesmo depois de ter migrado para um servidor dedicado. A grande verdade é que por um bom tempo ficamos devendo a devida atenção ao deployment do GUJ. Sempre que um problema acontecia alguém simplesmente reiniciava o servidor, sem investigar as causas reais do problema com profundidade.

Dada a relação próxima que a Caelum sempre teve com o site, já que dois dos fundadores do GUJ são também os fundadores da Caelum, resolvemos assumir de vez a posição de criadores do GUJ. A Caelum agora é a patrocinadora e mantenedora oficial do GUJ. Recentemente andamos gastando algum tempo, tentando acabar de vez com estes problemas que o GUJ a tanto tempo sofre. Felizmente, a melhora já é bem perceptível!

Há algum tempo atrás, o GUJ ficava esporadicamente muito lento. Para resolver estes problemas de lentidão, o primeiro passo foi conseguir um servidor dedicado, pago pelos anúncios espalhados pelo site. Já faz tempo que o GUJ roda neste servidor dedicado e desde então a performance se tornou quase sempre aceitável.

Porém, o servidor dedicado não resolveu todos os problemas, já que java.lang.OutOfMemoryError sempre foi o principal problema enfrentado pelo GUJ. Sempre desconfiamos que o culpado poderia ser o código do próprio GUJ, ou até do JFórum, que deveriam conter algum vazamento de memória.

Alguns desenvolvedores da Caelum já tiveram ótimas experiências passadas com o Jetty, que é um excelente servidor web e servlet contêiner. Foi, inclusive, um dos servidores Java pioneiros a usar conectores NIO (java.nio). O Jetty foi desenhado para ser embutido em outras aplicações Java e portanto é extremamente leve. Consome bem menos memória que o Tomcat, seu concorrente mais conhecido, e não deixa nada a desejar nas outras características.

O servidor dedicado do GUJ tem 2GB de memória RAM e o Tomcat estava configurado inicialmente para ter um heap máximo de 768MB (-Xmx768M). A primeira tentativa foi aumentar o heap máximo (-Xmx1024M), mas mesmo assim o temível OutOfMemoryError insistia em aparecer.

Resolvemos então dar uma chance ao Jetty. Já que ele consome menos memória, acreditamos que os OutOfMemoryError demorariam mais a aparecer. Logo ao subir, o jetty ocupa 4% da memória do servidor. O impressionante é que em duas semanas no ar, o uso total de memória não passou de 12%. Na verdade, o uso de memória estabilizou em 9% do total, porém recentemente fizemos alguns testes de carga no servidor do guj, com mais do que o dobro do número de conexões que o guj recebe hoje nos períodos de pico. Isto fez com que o uso de memória do Jetty pulasse para 12%. Uma diferença enorme da quantidade usada pelo Tomcat, que chegava facilmente a 80%.

uso de memória do Jetty

Já temos algum tempo rodando, sem problemas de memória e com o jetty estável usando 9-12% do total da memória do servidor. Já estamos até pensando em descartar a possibilidade de existir um vazamento de memória no código do GUJ ou do JForum. Isto tornaria o Tomcat culpado pelos problemas de memória!

A possibilidade de vazamentos de memória no Tomcat (estávamos com a versão 6.0.14, sem o APR e com Linux Kernel 2.6.22) deste tamanho é um pouco assustadora. Com a base de usuários que o Tomcat tem, muito possivelmente alguém já teria pego este problema muito antes de nós. Provavelmente o problema é de alguma configuração mal feita no Tomcat do GUJ.

Fato é que o Jetty foi uma tentativa que deu certo e o problema está aparentemente resolvido. O Jetty mostrou um uso mais alto de CPU que o Tomcat, chegando a picos de 60% da capacidade total de processamento do servidor, que possui 2 processadores. Através dos testes de carga que fizemos, temos percebido que o Jetty usa bastante CPU para responder diversas requisições simultâneas. Isso não chega a ser um problema, já que os dois processadores que o servidor possui são mais do que suficientes para atender a quantidade de requisições por segundo que o GUJ recebe hoje.

Apesar de termos algumas suspeitas, ainda não investigamos a razão do alto uso de CPU. Este é um ponto a ser abordado, caso o GUJ tenha problemas com disponibilidade de processamento algum dia.

Mesmo não representando um problema hoje, esta situação preocupa já que o MySQL rodando na mesma máquina também costuma usar bastante CPU. Isto pode se tornar um problema em algum dia que tenha um pouco mais de requisições por segundo do que o comum, já que o uso de CPU chegava as vezes perto do limite. Felizmente, grande parte das requisições ao servidor do GUJ são para conteúdo estático: imagens, JavaScripts, arquivos CSS, download de pdfs (dos artigos), entre outros. Todo esse conteúdo estático era servido pelo Jetty, contribuindo para o alto uso de CPU. Resolvemos então tentar um proxy reverso na frente do Jetty, especificamente para servir este conteúdo estático.

proxy reverso servindo conteúdo estático

Existem diversas alternativas de proxy reverso e a primeira a ser considerada quase sempre é o conhecido servidor web Apache Httpd, com a adição do mod_proxy. É uma excelente solução e existe bastante documentação para fazer tudo funcionar. No entanto, faz um tempo que eu já estava querendo testar o servidor russo Nginx, tão falado pelo pessoal da Engine Yard.

A desvantagem do Nginx para o Apache Httpd é a documentação não tão extensa. Esse é um grande problema para a comunidade do Nginx, que tem se esforçado em traduzir grande parte do que está escrito em russo. Apesar disso, o Nginx é impressionante e superou todas as nossas expectativas. Além de ser extremamente rápido, consome pouquíssimos recursos. Cada um dos processos (1 master + 5 workers) consome na maior parte do tempo apenas 1% de CPU e 0.2% da memória disponível do nosso servidor. Incrível!

O conteúdo estático do GUJ agora é todo servido pelo Nginx; as requisições nem chegam ao Jetty. Além disso, o Nginx, como um bom proxy reverso, oferece diversas otimizações. Uma essencial para o GUJ é o preenchimento automático dos cabeçalhos HTTP de cache, sugerindo aos browsers que façam cache do conteúdo estático. Agora o consumo de CPU diminuiu consideravalmente.

uso de recursos do nginx no GUJ

Não fizemos nenhum comparativo científico de performance, mas a melhora dos tempos de resposta do GUJ está visível. Frequentemente tenho a sensação de que o site está “voando”.

Configuramos também no Nginx a exibição de algumas estatísticas de acesso; tão valiosas ao GUJ. É assustador como o padrão de acesso se repete a cada dia, e a cada semana. Repare como o gráfico de requisições por segundo é praticamente idêntico para cada dia (com exceção do sábado e domingo, que são parecidos entre si).

requisições por segundo no GUJ, em uma semana

Temos também o interesante gráfico de conexões por segundo, que mostra relação média aproximada de 3 requisições por conexão.

conexões por segundo no GUJ, em uma semana

Note que a legenda do gráfico está errada, já que deveria mostrar “connections/sec”. O pequeno pico que dá para ver no gráfico aparece por causa de alguns testes de carga que fizemos neste dia. Pode ser desconsiderado.

Fizemos ainda algumas experiências com múltiplos servidores Jetty por trás do Nginx, funcionando também como balanceador de carga. Espero em breve postar sobre nossa experiência em geral com balanceamento de carga e alguns outros truques que pudemos testar nesta experiência do GUJ e em alguns clientes.

Aproveitando o post, o GUJ recentemente comemorou a marca de meio milhão de mensagens. É um orgulho poder fazer parte desta comunidade, e mais ainda por poder torná-la cada vez melhor.

Conexão Java 2007: Palestras, tutoriais e fotos

Por Sérgio Lopes em 13/11/07

Aconteceu no último final de semana o Conexão Java 2007, evento organizado pelo GUJ, PortalJava e Tempo Real Eventos. A Caelum esteve lá com stand, palestras e tutoriais.

Muito bom rever os amigos do GUJ, do PortalJava e ex-alunos da Caelum. As palestras foram ótimas e ainda tivemos os tutoriais. Paralelamente, o Philip comandava a muvuca com discussões acaloradas sobre arquitetura.

Lá no GUJ há um tópico onde o pessoal está colocando impressões sobre o evento e palestras (as nossas estão logo após as fotos).

Algumas fotos dos mini-cursos, do auditório e da muvuca lá fora:

Este ano, a Caelum ministrou 3 tutoriais e uma palestra no evento. Você pode baixar o material de cada um logo abaixo:

Repository: seu modelo mais Orientado a Objeto

Por Fabio Kung em 09/06/07

Já tem algum tempo que a excelente discussão no GUJ estava me motivando a escrever a respeito.

Para ambientar, a principal discussão é usar:

Fornecedor fornecedor = ...;
List contas = dao.carregaContasPagasDesde1999(fornecedor);


Ou:

Fornecedor fornecedor = ...;
List contas = fornecedor.getContasPagasDesde1999()


Já que posts na forma de diálogos costumam ser muito interessantes, aproveito um papo que eu e o Paulo Silveira tivemos.

Paulo: credo, achei horroroso isso da classe de domínio acessar o repositório
Paulo: mas fica bonitinha a sentença

Fabio: horroroso pq? vc prefere procedural?
Fabio: repositório é domínio tb
Fabio: um List, um Map são repositórios
Fabio: vc acha acessar List e Map horroroso?

Paulo: tem certa razão
Paulo: mas entao poderia chamar o DAO diretamente de repositório

Fabio: em muitos casos poderíamos
Fabio: eu só estava pensando num jeito legal de injetar

Paulo: desde que o cara não receba os objetos de dominio

Fabio: talvez com interceptor de session, ou listener
Fabio: ou então injeta nos métodos load()

Paulo: então. Complicado um Usuario sempre precisar de Repositorio!

Fabio: todo usuário não
Fabio: só Usuário Managed
Fabio: quando vc dá new num Usuário, vc tb não consegue navegar em relacionamentos
Fabio: só faz sentido pegar relacionamento em managed

Paulo: certo
Paulo: mas olha só
Paulo: vai ter Usuario com repositório e outro sem
Paulo: em alguns vc vai poder chamar o método que faz buscas, em outros não

Fabio: mas isso já é assim

Paulo: não com o dao

Fabio: imagina Usuario @OneToMany Categoria

Paulo: certo

Fabio: vc só consegue chamar usuario.getCategorias() no managed, mesma coisa
Fabio: só vai poder chamar usuario.getCategoriasEspeciais() no managed
Fabio: não adianta dar new Usuario().getCategoriasEspecias();

Paulo: hum… entendi! Podia ser um interceptor como fizemos no caelumweb
Paulo: aí ele setava um atributo privado

Fabio: Ou então o próprio load do dao/repository pode fazer isso.

Paulo: mas sei la, baita arquiteturazinha complicada

Fabio: nada! só injetar o repositório
Fabio: o resto é simples
Fabio: em vez de chamar no dao, que é procedural, chama via getter

Paulo: não não, você tem razão. É facílimo de implementar
Paulo: mas tipo, é muita coisa

Fabio: muita coisa do jeito que estão falando no tópico do GUJ! 500 camadas…
Fabio: Repository não precisa nem ser interface

Paulo: aquele interceptador que fizemos para gravar no lucene já não gosto muuuuito

Fabio: Repository pode ter referência para Session
Fabio: e não precisa injetar o repositorio na entidade com listener/interceptor
Fabio: poderia ser:

public class Repository {
  private final Session session;
  public Repository(Session session) {
    this.session = session;
  }
  
  public void get(Long id) {
    // poderia delegar para um dao, se fosse necessário
    Usuario u =  session.load(id);
    u.setRepository(this);
  }
}

Fabio: ou seja, na hora que você recupera uma Entidade managed, ela já vem com o repositório embutido…

Paulo: dessa maneira dá um pouco mais de responsabilidade para os beanzinhos entidades né? fica legal

Fabio: isso!

Paulo: proximo projeto vamos tentar essa abordagem?

Fabio: demorou… ;)
Fabio: hoje em dia, se vc quiser mostrar um objeto e alguma pesquisa personalizada, vc tem que consultar o objeto e a lista nas lógicas
Fabio: separados! E aí ejetar os dois
Fabio: muito feio
Fabio: geralmente faz-se isso (código usando algum controlador ruinzinho):

public class Logica // extends Action? ;)
  public void mostraFornecedorEContasPagas() {
    Long id = request.getParameter("id")// século passado...
    Fornecedor f = fornecedorDAO.load(id)
    List<Conta> contasPagas = contasDAO.listaContasPagasDoMes(fornecedor);
    // ejeta tudo para mostrar na view. No século passado seria:
    request.setAttribute("fornecedor", fornecedor);
    request.setAttribute("contasPagas", contasPagas);
  }
}

Fabio: MUITO procedural!

Paulo: cara se aplicarmos essas idéias, com velocity isso ia ficar ANIMAL
Paulo: Ia dar para chamar os métodos a la DAO direto no beanzinho

Fabio: ISSO!

Paulo: #foreach contas in contas.desde(1994)

Fabio: é meu, legal né?
Fabio: com JSP EL dá tb

Paulo: mais ou menos, teriamos de mexer nos evaluators

Fabio: não, soh usar padrão javabean
Fabio: ahhh vc quer passar parâmetro, aí EL não rola mesmo
Fabio: mas é puro DDD, já fiquei pensando bastante sobre o assunto
Fabio: ultimamente que tenho enxergado um jeito legal de aplicar

Paulo: bacana
Paulo: acho q vale a gente testar
Paulo: parava de ficar enfiando getters para expor as coisas para view.
Paulo: FABIO TODO: blogar sobre isso

Feito! ;)