Pular para o conteúdo
10 min de leitura

Cache HTTP na prática: Cache-Control, ETag e revalidação

Por Equipe Tech do Sonne ·

Como o cache HTTP realmente funciona: frescor, revalidação, ETag e as diretivas de Cache-Control que decidem o que é reusado sem baixar de novo.

Neste artigo

O cache é provavelmente a otimização de maior impacto que existe na web, e também uma das mais mal compreendidas. Quando bem configurado, ele faz uma página que já foi visitada aparecer instantaneamente, sem tocar na rede, porque o navegador reutiliza o que já tem guardado. Quando mal configurado, produz dois problemas opostos e igualmente frustrantes: ou o usuário vê conteúdo velho porque o cache não foi invalidado, ou o servidor recebe requisições demais porque nada é reaproveitado. A diferença entre esses cenários está inteiramente em alguns cabeçalhos HTTP que o servidor envia junto com cada resposta.

O grande equívoco é tratar cache como algo binário: ou o navegador guarda, ou não guarda. A realidade é mais rica e mais útil. O cache HTTP trabalha com dois conceitos distintos que operam em conjunto: o frescor, que determina por quanto tempo uma resposta pode ser usada sem sequer perguntar ao servidor, e a revalidação, que determina como confirmar de forma barata se uma resposta guardada ainda vale quando o frescor expira. Entender que são duas fases diferentes é a chave para configurar cache que é ao mesmo tempo agressivo e correto.

Neste artigo vamos destrinchar essa mecânica. Veremos como o Cache-Control define as regras, como o ETag e o Last-Modified permitem revalidar sem baixar tudo de novo, e como combinar essas peças para servir recursos de forma rápida sem nunca entregar conteúdo desatualizado. É uma otimização que rende muito e custa pouco, desde que você entenda o mecanismo.

Frescor: usar sem perguntar#

A primeira fase do cache é o frescor. Quando o servidor envia uma resposta, ele pode declarar por quanto tempo aquela resposta é considerada fresca. Durante esse período, o navegador usa a cópia guardada diretamente, sem fazer nenhuma requisição — nem sequer para perguntar se algo mudou. Do ponto de vista do usuário, o recurso aparece instantaneamente, porque a rede não é tocada.

O controle disso está no cabeçalho Cache-Control, mais especificamente na diretiva max-age, que informa em segundos por quanto tempo a resposta é fresca. Um valor alto significa que o navegador reaproveitará a cópia por muito tempo sem incomodar o servidor. Um valor de zero significa que a resposta expira imediatamente e sempre precisará ser revalidada antes do uso. Esse número é a alavanca principal de todo o comportamento de cache, e escolhê-lo bem depende de quanto o recurso muda.

O dilema do frescor: agressivo mas correto#

Aqui surge a tensão fundamental. Um max-age alto é ótimo para performance, mas perigoso para conteúdo que muda, porque durante todo o período de frescor o navegador servirá a versão antiga sem checar. Por outro lado, um max-age baixo mantém o conteúdo atualizado, mas desperdiça a maior vantagem do cache, que é evitar a rede. A saída elegante para esse dilema é separar os tipos de recurso. Arquivos cujo conteúdo nunca muda — porque o nome inclui uma impressão digital do conteúdo, mudando o nome sempre que o conteúdo muda — podem receber frescor longuíssimo com segurança, já que uma versão nova terá outro nome. Já o documento HTML que referencia esses arquivos recebe frescor curto ou revalidação obrigatória, garantindo que o usuário sempre pegue as referências mais recentes.

Revalidação: confirmar barato#

Quando o frescor expira, a resposta guardada não é jogada fora imediatamente. Em vez disso, entra a segunda fase: a revalidação. Em vez de baixar o recurso inteiro de novo, o navegador faz uma requisição condicional, perguntando ao servidor se a versão que ele tem ainda é válida. Se for, o servidor responde com um status especial e sem corpo, e o navegador reutiliza a cópia que já tinha. Só se algo realmente mudou é que o servidor devolve o recurso completo.

Essa é uma otimização poderosa porque a requisição de revalidação é minúscula e, quando a resposta é "nada mudou", nenhum byte de conteúdo trafega. O usuário paga apenas o custo de uma ida e volta pequena à rede, em vez do download completo. Para recursos grandes que mudam raramente, isso representa uma economia enorme.

ETag: a impressão digital do conteúdo#

O mecanismo mais preciso de revalidação usa o cabeçalho ETag. Quando o servidor envia um recurso, ele inclui um ETag, que é uma espécie de impressão digital daquela versão específica — tipicamente derivada do conteúdo, de modo que qualquer mudança gera um ETag diferente. O navegador guarda esse valor junto com a resposta.

Na hora de revalidar, o navegador reenvia esse valor no cabeçalho If-None-Match, essencialmente dizendo "eu tenho a versão com esta impressão digital; ela ainda é a atual?". O servidor compara com a versão atual: se baterem, responde 304 Not Modified sem corpo, e o navegador reaproveita sua cópia; se não baterem, responde 200 com o conteúdo novo e um novo ETag. Como o ETag é derivado do conteúdo, ele detecta mudanças de forma exata, sem depender de datas.

Last-Modified: revalidação por data#

Existe um mecanismo alternativo, mais antigo, baseado em tempo. O servidor envia o cabeçalho Last-Modified com a data da última alteração do recurso. Na revalidação, o navegador reenvia essa data no cabeçalho If-Modified-Since, perguntando se houve mudança depois daquele momento. O servidor responde 304 se nada mudou desde então, ou o conteúdo completo se mudou. É menos preciso que o ETag, porque depende da resolução da data e pode falhar em casos de mudanças muito rápidas ou de conteúdo que volta a um estado anterior, mas cumpre o papel para muitos casos. Quando ambos estão presentes, o ETag costuma ter precedência por ser mais confiável.

As diretivas de Cache-Control que importam#

O Cache-Control carrega várias diretivas além do max-age, e conhecê-las é o que permite configurar o comportamento com precisão.

  • no-store proíbe qualquer armazenamento: o recurso nunca é guardado, e toda requisição vai ao servidor buscar de novo. É o adequado para dados sensíveis que não devem sobrar em cache algum.
  • no-cache é enganoso: não significa "não guarde", e sim "guarde, mas sempre revalide antes de usar". A cópia é mantida, mas o navegador confirma com o servidor a cada uso. Combinado com ETag, dá conteúdo sempre atualizado com o custo mínimo de uma revalidação.
  • private indica que a resposta é específica de um usuário e só pode ser guardada pelo navegador dele, nunca por caches compartilhados no caminho, como os de uma rede de distribuição.
  • public autoriza explicitamente que caches compartilhados guardem a resposta, útil para conteúdo igual para todos.
  • must-revalidate reforça que, uma vez expirado o frescor, a cópia velha não pode ser servida sob nenhuma circunstância sem antes revalidar.
  • immutable promete que o recurso nunca vai mudar durante seu período de frescor, permitindo ao navegador nem tentar revalidar em recarregamentos.

Caches privados e compartilhados#

Vale distinguir onde o cache vive. O cache do navegador é privado, pertence a um usuário e pode guardar conteúdo personalizado. Já os caches compartilhados — proxies, redes de distribuição de conteúdo — ficam no caminho entre muitos usuários e o servidor, e servem a mesma cópia para todo mundo. Essa distinção é a razão de existirem as diretivas private e public: marcar como private algo destinado só àquele usuário evita o desastre de um cache compartilhado servir os dados de uma pessoa para outra. Confundir isso é uma falha de segurança, não só de performance.

O cabeçalho Vary e as variações da mesma URL#

Há uma sutileza que causa bugs difíceis de diagnosticar: uma mesma URL pode ter respostas diferentes dependendo de certos cabeçalhos da requisição. O mesmo endereço pode devolver conteúdo comprimido ou não, conforme o que o cliente aceita; pode devolver português ou inglês, conforme o idioma pedido; pode devolver a versão para um formato ou outro. Se o cache guardasse uma única resposta por URL, ele acabaria servindo a versão errada — entregando o conteúdo comprimido a um cliente que não sabe descomprimir, ou o idioma errado para outro usuário.

O cabeçalho Vary resolve isso dizendo ao cache quais cabeçalhos da requisição fazem a resposta variar. Quando o servidor declara que a resposta varia conforme a codificação aceita, por exemplo, o cache passa a guardar versões separadas para cada valor daquele cabeçalho, e só serve a cópia guardada para requisições que casam no mesmo valor. Esquecer de declarar corretamente essa variação é uma fonte clássica de bugs em que um usuário recebe a resposta destinada a outro contexto. É um cabeçalho discreto, mas fundamental para que o cache não misture respostas que só parecem iguais por virem do mesmo endereço.

Invalidação: o problema mais difícil#

Existe uma frase famosa que diz que as duas coisas mais difíceis em computação são nomear coisas e invalidar cache, e a segunda metade dessa piada é séria. Depois que uma resposta foi guardada com frescor longo, e o conteúdo por trás dela muda, como fazer o cache soltar a versão velha antes do prazo? A resposta honesta é que, para um cache que já entregou uma resposta fresca ao navegador do usuário, você não tem controle direto: aquela cópia será usada até o frescor expirar, e não há como alcançá-la remotamente.

É por isso que a estratégia de nomear arquivos com uma impressão digital do conteúdo é tão poderosa — ela transforma a invalidação em um não problema. Como cada versão do conteúdo tem um nome diferente, nunca é preciso invalidar nada: a versão nova simplesmente é um recurso novo, com outro nome, que o navegador ainda não tem e vai buscar. A versão velha continua no cache, inofensiva, porque ninguém mais aponta para ela. Em vez de tentar apagar o que está guardado, você garante que o que está guardado nunca precise ser apagado. Essa inversão — projetar para não precisar invalidar, em vez de invalidar — é uma das lições mais valiosas de todo o assunto de cache.

Montando uma estratégia coerente#

Com as peças na mesa, uma estratégia sólida emerge naturalmente da separação entre o que muda e o que não muda. Recursos versionados pelo nome — aqueles cujo nome de arquivo carrega uma impressão digital do conteúdo — recebem frescor longuíssimo e podem ser marcados como imutáveis, porque uma versão nova simplesmente terá outro nome e será baixada como um recurso diferente. Assim o navegador reaproveita a versão antiga para sempre, sem risco, e nunca precisa revalidar.

O documento de entrada, o HTML que aponta para todos esses recursos, segue a lógica oposta. Ele precisa estar sempre atualizado, porque é ele que decide quais versões dos outros recursos serão carregadas. Então recebe frescor curto ou revalidação obrigatória, tipicamente com no-cache mais ETag: o navegador guarda, mas confirma a cada uso, pagando só o custo de uma revalidação barata e recebendo 304 quando nada mudou. Dados de API ficam entre os dois extremos, com frescor ajustado à velocidade com que mudam e revalidação por ETag para o resto.

Fechando#

O cache HTTP é uma daquelas ferramentas em que um pouco de entendimento rende muito resultado. A distinção central — frescor para usar sem perguntar, revalidação para confirmar barato — organiza todo o resto. O Cache-Control define as regras de frescor e de comportamento; o ETag e o Last-Modified habilitam a revalidação condicional que devolve 304 em vez do conteúdo inteiro; e as diretivas finas ajustam onde e como cada resposta pode ser guardada.

O que torna esse conhecimento valioso na prática é que ele resolve os dois problemas opostos de uma vez. Configurando frescor longo para o que não muda e revalidação para o que muda, você consegue cache agressivo sem entregar conteúdo velho. O usuário ganha páginas que aparecem na hora, o servidor ganha alívio de tráfego, e você ganha a tranquilidade de saber que uma atualização vai chegar quando precisar. Não é preciso escolher entre rápido e correto: a mecânica do cache HTTP foi feita justamente para entregar os dois ao mesmo tempo, desde que você entenda em qual das duas fases cada recurso deve viver.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly