Web Sites de Alta Performance, Parte 3: Faça menos Requests HTTP

Este é o terceiro post da série Web Sites de Alta Performance. Esta série é baseada no livro de Steve Souders, High Performance Web Sites. Você pode ler a parte 1 e parte 2.

Como vimos no primeiro post desta série, apenas de 10% - 20% do tempo de carregamento de uma página é gasto com a geração do HTML que vai para o browser. Os outros 80% - 90% são gastos carregando os demais componentes da página (imagens, css, javascript, etc).

Portanto, uma primeira otimização que ataca estes 80% - 90% é diminuir o número de Requests HTTP que a sua página faz.

Quando você acessa uma página web, colocando o endereço da página no browser, esta página carrega o seu HTML que, por sua vez, faz outros Requests HTTP para carregar os demais componentes necessários: arquivos javascript, arquivos CSS, imagens, flash, etc. Para cada componente, é um novo Request Http que está sendo feito. E uma requisição Http tem todo um overhead do próprio protocolo Http. Ao diminuir o número de Requests, você está diminuindo o tempo total de carregamento da sua página.

Para conseguir diminuir o número de Requests, algumas coisas podem ser feitas:
  1. Uso de Image Maps:
    Image Map permite que se tenha apenas uma imagem com múltiplos links nesta imagem. Ou seja, não é necessário, ao criar uma toolbar por exemplo, ter várias imagens, uma para cada link. É possível ter apenas uma imagem maior, cujas partes desta imagem possuem diferentes links. Isso é feito através do uso de Image Map.

  2. Combinar arquivos em um único:
    Ao invés de fazer 4 requisições para 4 diferentes arquivos de JavaScript (JS), você pode combinar os 4 arquivos em um único, e fazer apenas uma requição Http. Em sendo um engenheiro de software, isso me parece estranho, pois vai contra a modularização. Mas, se pudermos ter esta modularização em tempo de desenvolvimento, e e combinar estes arquivos em tempo de deploy, teremos assim o melhor dos dois mundos.
    A mesma coisa vale para arquivos CSS: é possível combinar os diferentes arquivos CSS em um único. Combinar arquivos pode ser desafiador quando os scripts e arquivos css variam de página para página, mas tentar fazer esta junção em um único arquivo pode ser vantajoso. Se você conseguir fazer esta combinação automaticamente como parte do seu processo de Deploy, isso pode ficar transparente para o desenvolvedor, e assim conseguir fazer menos Requests HTTP, melhorando a performance final da sua página.
    Numa situação ideal, sua página poderia ter apenas um arquivo javascript e um arquivo CSS.
    Em algumas situações em que a página faz uso de diferentes bibliotecas javascript e ainda possui scripts próprios, a combinação pode dar ganhos em torno de 40% no carregamento da página.

  3. CSS Sprites
    CSS sprites é uma técnica usada para se criar apenas um arquivo de imagem que conbina várias imagens que serão usadas independentes umas das outras através de estilos CSS. Ou seja, existe apenas um arquivo de imagem que é resultado da combinação de algumas imagens a serem usadas independentemente. Para conseguir usar cada parte da grande imagem de forma a mostrar apenas a parte que interessa de cada vez, usa-se estilos CSS. Por exemplo:

    Desta forma, podemos ter uma imagem grande de, por exemplo, 200px por 200px, mas estamos apresentando apenas uma parte dela, de 26 por 26 px.
    O uso de CSS Sprites, combinado com o uso de Cache de imagens, é uma excelente forma de usar múltiplos ícones em uma página, porém fazendo apenas um Request para carregar a única imagem grande. Você pode ver mais a respeito deste assunto no artigo de Dave Shea chamado "CSS Sprites: Image Slicing's Kiss of Death".
A conclusão aqui, então, é: FAÇA MENOS REQUESTS HTTP.

Web Sites de Alta Performance, Parte 2: Visão Geral de HTTP

Este é o segundo post da série Web Sites de Alta Performance. Esta série é baseada no livro de Steve Sourders, High Performance Web Sites. Caso não tenha lido o primeiro, leia aqui.

Antes de apresentar cada coisa que pode ser feita para diminuir o tempo de resposta das suas páginas, vou fazer uma breve introdução sobre o protocolo HTTP, e principalmente as partes do Hypertext Transfer Protocol (HTTP) que podem afetar a performance.

A versão mais usada do protocolo é o HTTP/1.1. Mas ainda hoje alguns browsers e servidores ainda usam o HTTP/1.0.  Este é um protocolo cliente/servidor feito de requisições e respostas (Requests e Responses). O browser envia um Request por uma determinada URL, e o servidor que hospeda esta URL retorna com um Response. Os tipos de Requests são GET, POST, HEAD, PUT, DELETE, OPTIONS e TRACE. Focaremos no tipo GET que é o mais comum.

Um Request do tipo GET possui uma URL seguida de headers. Um Response contém um código de status (Status Code), headers e um corpo (body).  Veja um exemplo abaixo, onde a primeira parte é o Request, e a segunda o Response:

GET /js/algumScript.js
HTTP/1.1
Host: meudominio.com
User-Agent:Mozilla/5.0(...) Gecko/20061206 Firefox/1.5.0.9
------------------------------------------------------------
HTTP/1.1
Content-Type: application/x-javascript
Last-Modified:Wed, 22 Feb 2006 04:15:54 GMT
Content-Length: 355

var f = function(){}....

Compressão

O tamanho da Response pode ser reduzido  usando algum tipo de compressão caso ambos, browser e servidor,  tenham suporte para tal compressão. O browser diz que tem suporte a compressão para o servidor usando o header Accept-Encoding. O servidor, por sua vez, diz ao browser que a Response está com compressão ao usar o header Content-Encoding. Por exemplo, o browser pode enviar a Request com o header:
Accept-Encoding: gzip,deflate
Enquanto que o servidor envia a Response com o seguinte header:
Content-Encoding: gzip

Não entrarei em mais detalhes agora, fica aqui apenas esta visão geral mesmo.

Requests Condicionais

Caso o browser tenha uma cópia de um componente (CSS, Javascript, Imagem, ...) no seu cache, mas não sabe ao certo se esta cópia ainda está válida, um Request Condicional é enviado pelo browser. Caso a cópia do cache ainda seja válida, então o browser usa esta cópia local e não baixa novamente o componente, tornando o tempo de resposta mais rápido para o usuário final.

Normalmente o browser sabe se o componente é válido ou não através da data que o componente foi modificado pela última vez. E ele sabe esta data através do header Last-Modified presente na Response. Veja no exemplo de Response acima que foi usado deste header.

Para realizar um Request condicional, o browser envia o Request com o header If-Modified-Since, cujo valor é a data presente na última response no header Last-Modified.  Caso o componente não tenha sido modificado desde esta data, o servidor envia um Response com status code "304 Not Modified", e não envia body nenhum, resultando num Response mais rápido. No HTTP/1.1 ainda existem os headers ETag e If-None-Match para a realização de Requests condicionais. Mas não falarei em detalhes desses no momento.

Expires

Os Requests condicionais e status code 304 ajudam muito no tempo de resposta dos componentes de uma página, porém eles ainda precisam de um ciclo Request/Response entre o browser e servidor. o header Expires elimina esta necessidade deixando bem claro para o browser o prazo de validade do componente, e assim deixando para o browser a mensagem de que ele pode usar a cópia local que ele tem, até a data presente no Expires, sem fazer nenhum Request.

Keep-Alive

O protocolo HTTP é baseado no protocolo Transmission Control Protocol (TCP). No início, nas primeiras implementações de HTTP, cada Request precisava abrir uma nova conexão Socket. Isso era ineficiente pois muitos Requests HTTP de uma mesma página são feitos para um mesmo servidor. As conexões persistentes (Persistent Connections), tambéem conhecidas como Keep-Alive no HTTP/1.0,  foram criadas, então, para resolver esta ineficiência de abrir e fechar várias conexões Socket para um mesmo servidor. Portanto, browsers e servidores podem usar o header Connection para indicar o suporte ao Keep-Alive. Tanto o Request como o Response podem conter:
Connection: Keep-Alive

No HTTP/1.1, o header Connection: Keep-Alive não é mais necessário, mas muitos browsers e servidores ainda usam.

No HTTP/1.1, o que é apropriado de ser usado é o chamado Pipelining, que permite múltiplos Requests numa mesma conexão Socket, e é mais performático do que Persistent Connections. Porém, Pipelining não é suportado pelo Internet Explorer, pelo menos até a versão 7 do IE. E também não é ativado por padrão no Firefox até a versão 2. Por enquanto, então, Keep-Alive ainda é a forma mais eficiente de usar conexões socket para HTTP. Ao se tratar de HTTPS, isso é aida mais importante, pois estabelecer uma nova conexão HTTPS é ainda mais demorado.

O protocolo HTTP tem ainda muitos outros detalhes que podem ajudar a tornar suas páginas mais rápidas, mas não vou entrar em outros detalhes aqui. 

Este post foi apenas uma visão geral do protocolo HTTP. Não mostrei ainda como aplicar algumas técnicas que fazem uso dos conceitos apresentados aqui. Isso fica para os próximos capítulos desta série. Abcs

Web Sites de Alta Performance, Parte 1: A Regra do 80-20 da uma Página Web

Este post é o primeiro de uma série que irei denominar de Web Sites de Alta Performance. Esta série é inspirada e baseada no livro High Performance Web Sites de Steve Souders, na posição de Chief Performance Yahoo!. O livro é fino, leve e bastante objetivo. Esta série tem o objetivo de resumir as técnicas apontadas por Souders.


Assim como diversos engenheiros de software que conheço, eu já me dediquei bastante a otimizações no lado do servidor, como melhorias no uso de memória e índices de bancos de dados, etc. Porém, para a maioria de páginas web, menos de 20% do tempo de resposta para o usuário final é gasto levando o HTML de resposta do servidor para o browser. Portanto, ao otimizar algo no lado do servidor, você estará otimizando algo que consome menos de 20% do tempo total de resposta.

Para conseguir resultados visíveis para seus usuários, é preciso atacar a causa dos 80% restantes do tempo de resposta. Esta série mostra conceitos que explicam o funcionamento de páginas web e algumas técnicas capazes de tornar suas páginas mais rápidas.

Analisando a performance de uma página

Para você conseguir ver como uma página qualquer é carregada e o tempo que ela gasta para ser carregada, vou sugerir o uso do browser Chrome do Google. Ele possui uma ferramenta de análise recursos carregados pelo browser (resources). Ao abrir o Chrome, acesse o site que deseja. Vá ao menu cujo ícone é uma folhinha (ao lado direito da barra de endereço do Chrome), selecione Desenvolvedor e em seguida Console Javascript. O Console possui dois itens principais: Elements e Resources. Clique em Resources. Agora, vá para a janela do browser e e dê um Refresh. Após carregar a página novamente, volte para a janela do Console e veja o resultado.
É possível ver muito claramente quais são todos os recursos carregados pelo browser para que a página final seja apresentada corretamente para o usuário: Html da página em si, imagens, arquivos CSS, arquivos Javascript. Cada recurso desse é um request feito ao servidor para que ele seja encontrado e baixado. Uma ferramenta de análise desses recursos sendo baixados é muito importante para podermos ver como nossa página está hoje e como ela estará depois de aplicadas as técnicas que serão expostas nesta série de posts do meu blog. Acho que o Firefox também deve ter algum plugin que fornece este tipo de informação.

O que essas ferramentas irão fornecer é uma visão como a da figura abaixo (que é a análise do carregamento da home do www.yahoo.com):


Afinal, porque uma página demora para ser apresentada?

Ao analisar o carregamento de uma página (com uma dessas ferramentas citadas acima), é possível ver que uma página comum (como por ex.: www.yahoo.com), ao ser carregada pela primeira vez no browser (sem nada no cache), gasta pelo menos 80% do tempo de carregamento total com os componentes da página (imagens, arquivos css e javascript, etc). Quando eu digo carregamento total, quero dizer:
  • Request de cada componente;
  • Requests que não ocorrem em paralelo (scripts não são baixados em paralelo);
  • Parse de Html, CSS e Javascript;
  • Renderização final
Ver onde que o tempo é gasto é desafiador. Mas ver onde que ele não é gasto é fácil: o tempo de carregamento de uma página não é gasto em sua maioria no download do documento HTML que é gerado pelo processamento do servidor, incluindo lógica de programação no servidor, acesso a banco de dados, etc.

Portanto, a regra para se manter em mente aqui é: APENAS DE 10% - 20% DO TEMPO DE CARREGAMENTO DE UMA PÁGINA É GASTO COM A GERAÇÃO DO HTML QUE VAI PARA O BROWSER. OS OUTROS 80% - 90% SÃO GASTOS CARREGANDO OS DEMAIS COMPONENTES DA PÁGINA.


Servindo JavaScript e CSS com velocidade

Vale a leitura.

São dicas de como diminuir o tamanho dos arquivos Javascript e CSS, e como fazer algumas coisas para os browsers fazerem o Cache corretamente desses arquivos.


Abcs

InfoQ CheatSheets: dicas que ajudam

Eu sei, eu sei....
Tenho andado totalmente sumido.... essa vida tá a maior correria....

o InfoQ tem se tornado leitura obrigatória para mim. Muito bom, prático, com boas entrevistas, vídeos, PDFs, etc.

A última dica que vi no InfoQ foi descobrir o "FREE Cheatsheets for Developers". Muito legal.

Lá tem aqueles PDFs curtos, com dievrsas dicas. Cada PDF é sobre um assunto. No momento, são eles:

Não tenho trabalhado muito com Grails ultimamente (infelizmente). Adivinhe só. Tenho trabalhado com .NET. É isso mesmo....coisas da vida profissional.

Abcs

Detalhe do Grails com Eclipse

Pessoal

eu já passei maus bocados uma vez por causa da questão que irei escrever aqui. Hoje, meu companheiro de desenvolvimento João Paulo passou pelo mesmo problema...Depois de algum tempo ele resolveu me perguntar, e lendo o erro que ele me passou, não deu outra, era o problema que já passei.

Quando você ler um erro como este:

No signature of method: Requestmap.save() is applicable for argument types: () values: {}

(é claro que no seu caso a sua classe de domain não será Requestmap, será Pessoa, Categoria, Produto, sei lá o que)

Uma das possíveis causas disso é uma configuração no eclipse, que faz com que o Eclipse compile as classes groovy, gerando os arquivos .class . Neste caso, o grails, ao rodar a aplicação com "grails run-app"
, ele também compila, aí tudo se perde.

Não deixe de configurar o Eclipse para não compilar os arquivos de groovy. Para isso:
- Clique com botão direito no seu projeto, no eclipse
- Selecione "Groovy project Properties"
- Marque a opção "Disable Groovy Compiler Generating Class Files"

Abcs
Felipe

Grails 1.0.1 e many-to-many com GORM DSL

Oi pessoal,

primeiro queria divulgar o forum do GrailsBrasil ( www.grailsbrasil.com ). Um ótimo espaço para os brazucas, que cada vez mais descobrem e se encantam com o Grails.

Em seguida, queria falar, com certo atraso, da tão esperada Release 1.0 do Grails, que na verdade já está na 1.0.1. Isso é uma grande notícia.

Por último, a razão deste post....

O Grails 1.0.1 possui o GORM DSL, que oferece grande facilidade para controlar detalhes do mapeamento objeto relacional dos objetos de domínio. Ao migrar uma aplicação do grails 0.6 para o 1.0.1, quis alterar alguns detalhes do mapeamento, já que agoraeu conseguiria isso sem precisar apelar para mapeamento Hibernate com XML.

O problema que eu queria resolver era:
Digamos que eu tenha uma classe Usuario e outra Privilegio, com um relacionamento many-to-many entre elas. Logo teremos:


class Usuario {
String nome
static hasMany = [privilegios:Privilegio]
}

class Privilegio {
String nome
static hasMany = [usuarios:Usuario]
static belongsTo = Usuario
}

Com as classes acima, o grails gera as seguintes tabelas:
usuario
id
nome

privilegio
id
nome

usuario_priv
usuarios_id (que é uma FK para privilegio.id)
privilegios_id (que é uma FK para usuario.id)
É isso mesmo: o campo usuarios_id é uma chave estrangeira para o id da tabela privilegio; e o campo privilegios_id é uma chave estrangeira para o id da tabela usuario. A explicação pra isso está aqui (vejam o comentário do Graeme).

Eu não sei quanto a vocês, mas pra mim isso soa muito estranho. Mas é assim mesmo que o grails gera por padrão. A forma que eu gosto de trabalhar é com campos no singular e com o nome do campo da chave estangeira condizente com o nome da tabela de referência:
usuario_priv.usuario_id = usuario.id
usuario_priv.privilegio_id = privilegio.id

Para isso, então, eu antigamente teria que fazer o mapemanto com XML do hibernate (já fiz vários para outras aplicações com grails). Mas agora na versão atual posso fazer o que quero com o GORM DSL. E é isso que quero mostrar, pois há um detalhe que precisei fazer alguns testes até entender o comportamento do Grails com essa DSL. Vamos lá...

class Usuario {
String nome
static hasMany = [privilegios:Privilegio]

static mapping = {
privilegios column:'usuario_id', joinTable:'usuario_priv'
}
}


class Privilegio {
String priv
static hasMany = [usuarios:Usuario]
static belongsTo = Usuario

static mapping = {
usuarios column:'privilegio_id', joinTable:'usuario_priv'
}
}


O que eu acho curioso e anti-intuitivo no código acima, é que numa primeira tentativa eu coloquei:
class Usuario {
static mapping = {
privilegios column:'privilegio_id', joinTable:'usuario_priv'
}
}
Mas isso acabou sendo errado. Na verdade temos que colocar como nome da coluna (para ter o resultado que eu esperava), o nome da classe em que me encontro, e não o nome do atributo de relacionamento. Em fim: o correto é o código acima completo das classe Usuario e Privilegio, e não o código acima com fundo avermelhado.

Fiz questão de colocar isso aqui, pois a documentação não me parece muito clara quanto a isso. (talvez eu é que tenha entendido errado a documentação, sei lá....em fim....tá aí pra quem precisar deste detalhe).


Abcs
Felipe