Grails Brasil por dentro: sua arquitetura

Grails Brasil 2.0

Sem dúvidas meu projeto mais conhecido (além deste blog) é o Grails Brasil. Tenho muito orgulho deste trabalho porque diariamente vejo uma quantidade enorme de gente que ajudei com ele.  Há um aspecto no Grails Brasil que chama a atenção de diversos visitantes: é um site extremamente rápido. Neste post vou expor alguns dos seus segredos que, espero, irão contribuir de uma vez por todas para acabar com o mito de que “Grails é lento” ou que “não serve para aplicações com grande número de acessos”. Mas primeiro um pouco de história.

Pequeno histórico

Em 2007 (final de 2006 pra ser exato) eu já acompanhava o projeto Grails e achava que era a promessa para o desenvolvimento de aplicações corporativas na plataforma Java EE. Se tudo desse certo eu poderia aproveitar todo o meu código fonte legado tendo os ganhos de produtividade que o pessoal do Ruby on Rails estava tendo. Uniria o melhor de dois mundos. Como podem ver, a promessa se cumpriu, mas naquela época havia um pequeno problema: quase ninguém no Brasil conhecia Grails.

Eu tinha duas opções: ficar sentado esperando que algo surgisse ou tomar uma atitude. Foi assim que no horário do almoço o Grails Brasil surgiu em sua primeira versão, baseada no phpBB. Usando o assistente da HostNet em 15 minutos eu tinha uma primeira versão do site pronta para uso. Muitos me criticaram por criar um fórum sobre Grails em php, mas minha resposta ainda é a mesma: até então eu não conhecia nenhum motor de fórums que superasse o phpBB (confesso que ainda não conheço). Sendo assim, por que eu deveria reinventar a roda? Uma das principais virtudes de uma arquitetura não é o bom reaproveitamento de código? Bom: este é assunto para outro post. O importante é que por um bom tempo o phpBB supriu todas as minhas necessidades.

Infelizmente com o passar do tempo comecei a ter diversos problemas com spam. Por mais que eu tunasse o phpBB com plugins de segurança, sempre ocorriam ondas de spam que tornavam a vida dos participantes do fórum um inferno. Os ataques eram tão intensos em um dado período que comecei a ter perda financeira, pois o site estourava muito seu limite de banda mensalmente sendo que 90% do tráfego eram bots. Havia chegado a uma encruzilhada: ou encontrava uma solução para o problema ou fechava o site.

A solução foi reescrevê-lo do zero tendo como meta dois objetivos:

  • Acabar com o problema do spam
  • Minimizar ao máximo os custos de hospedagem do Grails Brasil

Solução: reescrever do zero – a visão macro da arquitetura

Foi quando resolvi recriar o site em Grails com base em alguns princípios que expliquei neste post. Por um breve momento pensei em usar o JForum como solução, mas eu queria mais: meu desejo era tornar o Grails Brasil um experimento de software cujo objetivo fosse criar a aplicação mais leve possível feita com o framework (usei a versão 1.3.7).

O primeiro passo foi escolher o serviço de hospedagem. Optei pelo EC2 da Amazon no qual eu pagaria pelo processamento da minha aplicação. Esta escolha se mostrou uma vitória para o projeto pois me incentivou a escrever código altamente otimizado para que os custos ficassem mínimos. Como alta disponibilidade não era um dos requisitos do projeto, optei por inicialmente usar uma única instância de 600 Mb de memória RAM. Estas restrições aumentaram meu tesão pelo projeto, pois com isto escrevi cada linha do código fonte pensando em como minimizar ao máximo o uso de recursos computacionais (um exercício interessante que recomendo a qualquer um que se interesse por otimização de software).

Como a maior parte das consultas ao Grails Brasil é textual um novo elemento foi adicionado ao projeto: uso o Lucene como indexador de qualquer conteúdo adicionado no site.  O diagrama abaixo mostra uma versão bem simplificada da arquitetura final.

Todo o conteúdo ainda é armazenado no MySQL e obrigatoriamente indexado pelo Lucene. Optei por não usar o plugin searchable, por duas razões: tive alguns problemas com ele em projetos passados e era também uma excelente oportunidade para que eu pudesse me aprofundar no Lucene. Reinventar a roda neste caso foi uma experiência “pedagógica” que se pagou bem no meu caso, pois minha compreensão desta ferramenta cresceu muito e, com isto, pude aplicá-la a outros projetos com bastante sucesso.

Como banco de dados, optei pelo meu favorito que é o MySQL. Naquela época a versão 5.5 ainda era recente. Como o projeto era meu, me dei o direito de experimentar o “brinquedo novo” e fiquei bastante feliz com os resultados. Comparado a versão 5.1 é um upgrade praticamente obrigatório.

Plugins usados

Code Coverage: apesar dos bugs existentes na versão atual, o Grails Brasil possui mais de 90% do seu código fonte coberto por seus testes automatizados. Os testes serviram dois propósitos, o óbvio e como profilamento do sistema. Com eles ficava fácil identificar todos os gargalos do sistema e, assim chegar mais próximo do objetivo principal do sistema.

FckEditor: posteriormente usei o FckEditor como editor básico dos fórums e dos casos de sucesso. Foi uma escolha acertada, pois o plugin me permitiu customizar seu comportamento até que chegasse no ponto que eu considerava “perfeito” para o site.

Feeds: tudo no Grails Brasil gera RSS graças a este plugin, que foi uma mão na roda. Primeiro fiquei com um pé atrás com ele, porém após uma análise minunciosa do seu código fonte vi que o bichinho era simplesmente lindo.

Quartz: diversas tarefas no Grails Brasil são executadas assíncronamente, o que propicia uma experiência de uso muito mais fluída para o usuário. Este plugin literalmente salvou a experiência de uso dos visitantes.

Ausência interessante: Spring Security. O leitor atento pode se perguntar a razão pela qual não inclui este plugin. Uma série de fatores contribuíram para isto. Pra começar, o Grails Brasil não possui uma área administrativa. Sendo assim, o controle de autenticação/autorização é bastante simples: usuário só tem acesso de escrita se é cadastrado e está autenticado no site (escrevi uma suite de testes imensa para garantir a segurança do sistema).  Além disto, com as minhas restrições de memória, o overhead de inicialização do plugin é significativo. Sim: ele consome poucos recursos do sistema e seu custo sob a performance é mínimo, mas apenas depois que o sistema foi inicializado.

Princípios de implementação

O pai inconsequente

Aplico uma técnica que chamo de “pai inconsequente”. É bastante simples: o filho sabe quem é seu pai, mas jamais o contrário. Por exemplo: ao invés de escrever algo como o código abaixo:


class Pai {
static hasMany = [filhos:Filho]
}
class Filho {
static belongsTo = [pai:Pai]
}

Escrevo apenas o belongsTo na classe Filho. Com isto obtenho um ganho de performance bastante interessante na listagem dos registros pai (como por exemplo os posts na listagem do fórum ou a descrição dos cases na seção de cases). Sim, eu sei que estou lidando com uma classe do tipo proxy aqui, e que os filhos só são carregados se o atributo correspondente é invocado pela primeira vez. No entanto, isto me garante a geração de classes proxiadoras menores e, consequentemente, que consomem bem menos memória.

Outra vantagem é que as dependências entre minhas classes de domínio sempre terão a forma de uma árvore, ao invés de um ciclo. Com isto a modularização do código caso seja necessário por exemplo extrair um plugin da aplicação se torna muito mais fácil.

YSlow na veia

O Yahoo tem uma suite de testes de performance maravilhosa chamada YSlow. Tentei aplicar praticamente todos os princípios no Grails Brasil. Ainda não está nível A, mas caminhamos para isto :).

  • Arquivos estáticos como imagens são carregados a partir de um outro servidor de conteúdo estático (o famigerado CDN: Content Delivery Network)
  • Diversos recursos são minimizados quando aplicável (javascript por exemplo)
  • As diretrizes básicas são seguidas religiosamente, como por exemplo inclusão de CSS no topo da página e Javascript no final

Tuning de banco de dados

Nem sempre o GORM irá criar todos os índices necessários em suas tabelas para obter performance máxima. Gastei um bom tempo analisando as consultas feitas pela aplicação contra o MySQL e criando os índices necessários. Os resultados, como já eram de se esperar, foram excelentes.

Programação dinâmica

Há um cacheamento físico de algumas partes do sistema. Por exemplo: o RSS de algumas partes do site não é gerado dinâmicamente, mas apenas na primeira vez em que é requerido. O resultado é persistido em disco (ou cacheado temporariamente em memória) e só é alterado quando há necessidade. Com isto reduzi significativamente o número de vezes que precisei acessar o banco de dados.

A regra é clara: se é caro de gerar, gere o mínimo de vezes possível.

Se pode ser feito depois, deixe pra mais tarde

Para garantir a experiência de usuário mais fluída possível, diversas das operações executadas pelo site são executadas apenas assíncronamente. Um bom exemplo é a integração com nosso servidor SMTP. Quando alguém posta uma resposta a uma pergunta, um e-mail é enviado para o autor do post e também para aqueles que se inscreveram nele, porém este e-mail não é enviado imediatamente.

Há uma série de pilhas no Grails Brasil para processamento assíncrono (implementadas como tabelas no banco de dados) de mensagens. Periodicamente um job lê as mensagens na pilha e as processa em lote. Com isto tenho a possibilidade de implementar mecanismos de retentativas em caso de erro e também um aproveitamento melhor de conexões com recursos externos.

Big Brother no Grails Brasil

No primeiro release da segunda versão do Grails Brasil observei que diversas das otimizações que havia implementado eram completamente desnecessárias. Sim, aquele papo de que “otimizar antes é furada” é real, BEM real.  A solução que encontrei foi incluir um sistema de log no sistema que me fornece a medição de performance de cada método executado.

Com isto pude entender muito melhor o comportamento dos usuários do sistema. Consequentemente eu sabia exatamente aonde uma otimização podia ou não valer à pena. Os logs foram fundamentais para que eu descobrisse, por exemplo, o que valia à pena cachear ou não. Os algoritmos de cacheamento que implementei após o log se mostraram ordens de magnitude mais eficientes.

Em números e minha lista negra

O Grails Brasil tem hoje algo em torno de 4000 a 6000 acessos/dia válidos. Destes, menos de 5% são ativos, ou seja, ficou nítido que as pessoas usam o site muito mais como mecanismo de pesquisa do que de participação efetiva. O consumo de memória raríssimas vezes supera 300 Mb e temos em média 60 a 100 sessões concorrentes a qualquer momento do dia ou da noite.

Por acessos válidos eu quero dizer “não spam”. Via spam o acesso ainda é brutal: basicamente o triplo do que mencionei. Detecto o SPAM pelo país de origem. Dado que o site se chama Grails Brasil, dou prioridade aos IPs provenientes de países que possuam língua portuguesa ou que não estejam no meu blacklist (amigos chineses, agradeçam à ChongSoft por estarem na lista negra). Quando um IP está na lista negra, este possui tratamento diferenciado, minimizando o consumo de recursos do sistema.

Conclusões

Sei que alguns dos pontos que apontei neste blog são controversos (como a estratégia do “pai inconsequente” por exemplo), porém minha experiência demonstrou que funcionam muito bem. Claro: ainda há bugs no site que preciso arrumar e, lá no fundo, ainda acredito que dê pra melhorar ainda mais esta performance.

Mas o interessante é que o Grails Brasil serve como um excelente exemplo de que sim, é possível escrever uma aplicação Java EE consumindo o mínimo possível de recursos computacionais e, ainda por cima, apresentando uma performance excelente. Vale salientar também aqui o papel da pesquisa: só com muita sorte eu conseguiria os resultados que obtenho no Grails Brasil se não tivesse empenhado boa parte do meu tempo pesquisando soluções para os problemas que encontrava.

Outro ponto fundamental: otimização prematura normalmente gera retrabalho. Quando for lançar a primeira versão do seu projeto, certifique-se de possuir uma excelente cobertura de log. Somente com dados reais de produção é que podemos encontrar os gargalos de nossos sistemas. Caso contrário, você cometerá os mesmos erros que eu, desperdiçando boa parte do seu tempo.

E bom, é isto ai. Espero que as dicas que passei neste post sejam úteis a vocês que desejam tirar máximo proveito dos seus servidores sem pagar muito.

PS:

o mais gostoso. Gasto menos de US$ 1,00 mensal de hospedagem na Amazon com o Grails Brasil ;)

Update 12/12/2012

Eu pagava US$1,00 mensal com o plano Free Tier da EC2, que durou um ano.
Continuo usando uma micro instância após o fim do plano. Quanto pago hoje? Algo entre 17 e 20 dolares mensais.
Continua valendo muito à pena

29 thoughts on “Grails Brasil por dentro: sua arquitetura

  1. Muito bacana o post, Kico. Sempre quis entender como era o forum ‘nos bastidores’. Valeu pelas excelentes dicas!

  2. Parabens Henrique.

    Pra ficar mais didático poderia disponibilizar o Código Fonte do Grails Brasil também. Nos ajudaria bastante ver como você desenvolveu a aplicação.

    Fica a dica.

  3. Oi Ilmon, valeu pelo toque.

    Com relação à liberação do código fonte do Grails Brasil, ainda é uma discussão aberta, pois preciso resolver alguns interesses comerciais para decidir se vou realmente fazer isto ou não.

  4. Bem legal o case. Só duas perguntas:
    1. Que tipo de tratamento diferenciado vc dá aos IPs na lista negra?
    2. Fiquei intrigado com valor da Amazon. Pra pagar isso por mês vc comprou uma instancia ou tá só no ondemand? Criou o servidor do zero ou usou o beanstalk? Usa RDS?

  5. Oi Rodrigo.

    Respondendo
    1. Se for cadastro de membros, eu não os aprovo imediatamente. Se for de um IP da lista negra, eu simplesmente mando uma resposta vazia

    2. No caso do AWS, eu estou usando o serviço mais simples que você possa imaginar. Apenas uma instância no EC2 criando um servidor do zero. Não uso RDS ainda. O único serviço que quase comprei de cara foi o de envio de e-mails. Incrível né? :)

  6. Kico, você usou qual ferramenta para desenvolver? textmate? intellij? Spring STS?

    []`s

  7. Muito bom o post, parabens !!!

    Algumas observações:

    1. O pai inconsequente : como vc trata a situação de carregar um pai e seus dependentes ? visto que o pai não conhece os filhos.
    2. Build da aplicação: Gradle, ant, ivy o que vc usa?
    3.Ambiente de desenvolvimento: Eclipse, netbeans, etc.
    4. Código fonte: Se por algum motivo não for possível a liberação dos fontes, que tal um projeto ao estilo do appfuse, onde vc aplicaria todas as suas técnicas em cases simplificados.

    Abraços

  8. Oi Mauricio, legal que tenha gostado. Bom, às respostas :)

    1. Neste caso são feitas duas consultas. Uma que retorna o pai e outra com os filhos. São passadas duas variaveis para a visualização: o pai e a listagem dos filhos. Menos produtivo, mas a performance se paga.

    2. O próprio build nativo do Grails. Bem simples o negócio.

    3. Notepad++ e linha de comando

    4. É uma excelente idéia! Mas não sei como eu abstrairia estas práticas ainda. O melhor que consegui fazer até agora foi este post. :)

  9. Raríssimas vezes preciso usar o depurador, porque tenho uma base de testes que cobre uns 80% do código do Grails Brasil.

    E como cada método possui o seu próprio teste unitário/integrado, a cada implementação, executando os testes necessários consegui garantir a integridade do todo.

    Além disto, tenho também um sistema de log bem completo, a partir do qual fico sabendo de tudo o que é feito dentro do código.

    Caso uma depuração seja realmente necessária, ai eu uso o STS mesmo.

  10. Olá Kico, parabéns pelo trabalho.

    Poderia dizer qual foi o plano optado e as configurações que você utilizou na Amazon EC2?

    Obrigado

  11. Adorei as dicas. Muito obrigada por compartilhar seu conhecimento. =D

  12. Cara muito legal esse post.

    Aproveitando, gostaria de tirar uma dúvida. Você disse que usou o Lucene para pesquisas, como você está rodando o sistema na cloud da amazon, gostaria de saber como você está armazenando os índices? no próprio sistema de arquivos em um servidor dedicado. Pergunto isso, porque hoje tenho uma aplicação no Heroku, e nele eu não posso armazenar os índices no sistema de arquivos e como estou usando o Hibernate Search, não consegui uma maneira legal de integrar com os add-ons do Heroku. Se puder falar um pouco mais desse aspecto do Grails Brasil e me passar umas dicas

  13. Oi Mayko,
    por enquanto no próprio sistema de arquivos.

  14. Kiko, você menciona o uso de outro servidor para conter os Arquivos estáticos da aplicação.
    Estou tentando usar a mesma abordagem para uma aplicação da organização na qual trabalho.
    Entretanto, estou tendo problemas pois usamos protocolo ssl e alguns navegadores reclamam q

  15. … post anterior reescrito…
    Kiko, você menciona o uso de outro servidor para conter os Arquivos estáticos da aplicação: js, images, css, etc.
    Estou tentando usar a mesma abordagem para uma aplicação da organização na qual trabalho.
    Entretanto, estou tendo problemas pois usamos protocolo ssl e alguns navegadores reclamam e não fazem o download do conteúdo estático pelo fato de minha aplicação usar o protocolo ssl e o servidor onde estão armazenados os arquivos estáticos não usar.
    Você já enfrentou este problema?
    Encontrou alguma solução?

    Grato, Weber Miranda

  16. Opa,
    neste caso com SSL não. Agora, para evitar problemas de xss, normlamente há alguns cabeçalhos que você adiciona nas requisições http pra evitar isto (infelizmente eu não estou com eles aqui em mãos agora para te passar na hora, mas buscando no Google por isto, “xss http header” você encontra)

  17. Olá Kiko,

    Bom, como eu sei que um bom design de software juntamente com sua arquitetura inteligente se perpetua, vale o comentário mesmo que atrasado quase 1 ano.

    Excelente construção, posts assim enriquecem a comunidade.

    Valeu cara, aliás estou com mais dois posts seus na fila pra ler mais de uma vez e comentar, fora o miocc que gostaria de escrever algo, pois o usei em 2 pet’s meus.

    Esse Kiko manda bem !

  18. Seria show um post técnico de o que logou, como logou, para medição de performance.

    Abracos

  19. É uma excelente idéia Mr. Renan!
    E sabe de uma coisa? Visto que estou atualmente fazendo uma série de atualizações no site (muitas delas vão ser profundas), pode ser que isto vire um vídeo ao invés de um post hein? ;)

  20. Excelente texto, mesmo para quem – assim como eu – tá chegando agora aqui. Valeu Kiko!

  21. No caso do Grails, a compatibilidade é a mesmíssima que a do Java, dado que é executado no topo da JVM.

Leave a Reply

Your email address will not be published. Required fields are marked *