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