Em tempos de alta demanda, o cache pode tornar-se o melhor amigo das aplicações web. Através dele temos uma opção prática, acessível e barata para melhorar performance, diminuir consumo de recursos e tempos de resposta. Qualquer milissegundo economizado é um ponto a mais com o seu usuário, com os mecanismos de busca e com o seu serviço de hospedagem.
Antes de falarmos de cache em aplicações Django ou Codeigniter, acho interessante falarmos sobre o uso de cache com o protocolo HTTP. Afinal, é esta a primeira camada a “atacar” quando precisamos melhorar os tempos de resposta das nossas aplicações web.
Segundo Kalid Azad:
Caching is a great example of the ubiquitous time-space tradeoff in programming. You can save time by using space to store results.
Basicamente, caching é o ato de “economizar processamento” armazenando os seus resultados. Um bom exemplo é o temporário do seu navegador, onde uma imagem que não teve alteração desde o momento do seu download é resgatada do seu disco e não da internet. Uma tarefa mais rápida e menos custosa.
Logo, entendemos que o cache é um local em disco ou memória utilizado para armazenar estes resultados. Ele pode se aplicar ao front-end (como ilustrado no exemplo acima), ou ao back-end, através dos servidores web, como o Apache e o Nginx, ou através de ferramentas mais específicas, como Memcached e Redis.
Assim como a definição de cookies e sessões, a utilização de cache nos navegadores Web é feita através de informações transmitidas pelo cabeçalho da requisição e resposta.
Existem quatro tipos de cabeçalhos específicos para cache em HTTP. Mas todos partem da premissa que o arquivo em questão (pode ser um documento, imagem, script, etc) já está armazenado no disco do internauta, acessível ao navegador.
Com o Last-Modified, o navegador informa ao servidor que irá baixar
um arquivo desde que a sua data de modificação seja diferente da data do
arquivo armazenado. Na requisição é passado o cabeçalho
If-Modified-Since, e se a data do arquivo no servidor for mais
recente, o navegador faz um novo download.
Vamos fazer uma requisição tendo como resposta um cabeçalho
Last-Modified:
$ curl -i -I http://klauslaube.com.br/media/blog/security.jpg
HTTP/1.1 200 OK
...
Date: Tue, 01 May 2012 19:20:27 GMT
Last-Modified: Sat, 07 Apr 2012 17:51:10 GMT
...Uma vez que o arquivo esteja em disco, o navegador tem como informar a
data da última alteração. Então, fazemos uma nova requisição ao arquivo
security.jpg, passando esta data no cabeçalho If-Modified-Since:
$ curl -i -H "If-Modified-Since: Sat, 07 Apr 2012 17:51:10 GMT" http://klauslaube.com.br/media/blog/security.jpg
HTTP/1.1 304 Not Modified
Date: Tue, 01 May 2012 19:22:00 GMT
Last-Modified: Sat, 07 Apr 2012 17:51:10 GMTA resposta 304 Not Modified não traz o conteúdo do arquivo em seu
corpo, e é através desta resposta que o navegador sabe que não precisa
fazer o download do arquivo, utilizando assim a versão que está em seu
cache.
O modo como a ETag funciona é bem parecido com o conceito do
Last-Modified. A diferença está no método de comparação: ao invés de
fazer comparações pela data, são realizadas comparações através de
identificadores únicos, atribuídos aos arquivos envolvidos nas
requisições.
Quando trabalhamos com ETag, obtemos respostas com o seguinte
cabeçalho:
$ curl -i -I http://localhost/exemplo-cache.html
HTTP/1.1 200 OK
...
Date: Tue, 01 May 2012 19:46:18 GMT
ETag: "2c6b0d8-13-4befe555d6f80"
...É através do valor 2c6b0d8-13-4befe555d6f80 que navegador e servidor
saberão se aquele arquivo em questão já está armazenado em cache. Isso
é possível através do cabeçalho If-None-Match, enviado pelo
navegador na requisição:
$ curl -i -I -H "If-None-Match: \"2c6b0d8-13-4befe555d6f80\"" http://localhost/exemplo-cache.html
HTTP/1.1 304 Not Modified
Date: Tue, 01 May 2012 19:50:40 GMT
ETag: "2c6b0d8-13-4befe555d6f80"Uma vez que o valor bata com o identificador do arquivo, o servidor informa ao navegador que não houve alterações. Então, o navegador utiliza a versão do arquivo que está no temporário.
A grande desvantagem dos dois métodos acima é que necessitamos consultar
o servidor para verificar a procedência do arquivo. Com o Expires e
max-age a “data de validade” vem junto com a requisição, logo, o
navegador já sabe quando o arquivo em seu cache irá expirar, e só
voltará a consultar o servidor quando este tempo for alcançado.
Com o Expires, o servidor retorna no cabeçalho da resposta uma data
de validade para um determinado arquivo:
$ curl -i -I http://klauslaube.com.br/media/blog/cookies.jpg
HTTP/1.1 200 OK
Date: Tue, 08 May 2012 01:49:13 GMT
...
Expires: Thu, 31 Dec 2037 23:55:55 GMT
...Solicitando uma nova requisição para este mesmo arquivo, o navegador
analisará a data local e a data de expiração. Se a data atual for maior
que Expires, aí sim o navegador se comunicará com o servidor web,
e fará um novo download do arquivo.
Embora o max-age possa parecer um pouco enigmático, ele é (na minha
opinião) uma solução mais elegante e fácil de implementar que o
Expires.
Com o Expires, temos que informar uma data “absoluta” no cabeçalho,
ou seja, somos obrigados a dizer o dia da semana, mês, ano, hora, minuto
e até mesmo segundo em que determinado arquivo irá expirar. Logo, temos
o trabalho de interpretar a data da requisição (seja no servidor ou na
aplicação) adicionando o tempo que desejamos de cache e imprimindo
este valor por extenso.
Com o max-age temos a opção de utilizar datas “relativas”, ou seja,
podemos dizer ao navegador que o arquivo irá expirar em 1 dia (em
segundos):
$ curl -i http://localhost/exemplo-cache.html
HTTP/1.1 200 OK
Date: Mon, 14 May 2012 17:04:29 GMT
...
Cache-Control: max-age=86400, must-revalidate
...Como você já deve ter reparado, não existe um índice de cabeçalho
específico chamado max-age. Ele é na verdade um valor do índice
Cache-Control.
O valor must-revalidate solicita aos mecanismos de cache (você
pode estar “atrás” de um proxy) o seguinte: Quando o arquivo
ultrapassar o max-age, o user-agent deve revalidar o conteúdo
junto ao servidor Web. Embora esse seja o comportamento esperado por
estes mecanismos, tornar esta informação explícita pode garantir que
ferramentas mais “obscuras” sigam este comportamento.
O Cache-Control foi adicionado na especificação do
HTTP 1.1 com a finalidade de contornar
as limitações do Expires, e também de melhorar o controle sobre o
cache de determinado conteúdo por diferentes tipos de mecanismos.
Além do uso do max-age, é através do Cache-Control que podemos
especificar o comportamento de cache para o navegador (private),
para algum proxy, servidores intermediários ou requisições HTTPS
(public), ou ainda informarmos que não queremos fazer caching do
conteúdo (no-cache).
Leia mais sobre Cache-Control.
Vale ressaltar que o Cache-Control tem precedência sobre o
Expires.
A resposta é: depende do cenário.
Para servir arquivos estáticos, a Webfaction utiliza os cabeçalhos
Last-Modified, Expires e max-age, atribuindo aos dois
últimos valores absurdos de cache (por exemplo, datas de expiração
para o ano de 2037). Isso garante que o seu navegador, proxy ou
gateway “nunca esqueça” de uma determina imagem, folha de estilos ou
arquivo Javascript.
Mesmo com o uso do max-age, é interessante ter o Expires como
alternativa, caso o navegador do internauta não compreenda instruções de
Cache-Control. Já a utilização do Last-Modified, segundo o Ask Apache,
não é lá muito interessante pois a sua utilização faz com
que alguns navegadores ignorem o cabeçalho Expires. Um argumento
mais relevante é a eliminação de procedimentos de validação (como o
If-Modified-Since e If-None-Match), deixando a cargo apenas do
Expires e max-age determinar o tempo de vida do estático em
cache.
Em páginas dinâmicas, onde um cache de 5 ou 10 minutos possa ser
aplicado, o max-age com Expires é fundamental. Já em páginas que
necessitam de um conteúdo em tempo real, ou páginas que utilizam
informações de cookies ou sessões, a ausência de cache é
justificável.
Em documentos HTML onde o conteúdo é atualizado com uma frequência
indeterminada, o uso de Last-Modified ou ETag é mais apropriado.
Uma vez que fica difícil determinar quando a atualização irá ocorrer, é
uma boa estratégia fazer com que o navegador atualize o conteúdo do seu
cache quando necessário.
Embora seja um conteúdo bem introdutório, acho fundamental sabermos as diferentes maneiras de aplicar cache com o protocolo HTTP antes de partirmos para soluções específicas. Essas recomendações não são infalíveis, e em um cenário mais “extremo”, necessitam sim de auxílio de algumas ferramentas disponíveis no mercado para tornar o cache eficiente tanto para a experiência do usuário, quanto para a estabilidade dos seus serviços.
Embora eu tenha utilizado o navegador Web como foco das explicações,
“robôs”, ferramentas de proxy e gateways também podem fazer controle
de cache. O comportamento é basicamente o mesmo, variando de acordo
com instruções passadas no cabeçalho Cache-Control.
Como não sou nenhum “expert” no assunto, se você possui alguma sugestão ou correção sobre o uso de cache com HTTP, por favor, conte-nos através dos comentários abaixo.
Até a próxima…