Meu CMS gera páginas bastante complexas e, portanto, demora um pouco para fazê-lo (cerca de 2 segundos), o que está bem acima do meu orçamento de tempo para fornecer páginas ao cliente.
Porém é muito barato para mim saber a versão atual de uma determinada página e é muito fácil para mim dizer se uma determinada versão ainda está atualizada. Como tal, eu gostaria de poder colocar uma estratégia baseada em ETag onde cada solicitação para uma página precise ser revalidada, mas o servidor responderá em no máximo 10 ms se o conteúdo não mudar.
Para que isso seja eficaz, preciso compartilhar esse cache entre todos os meus clientes. Enquanto a ETag for revalidada, todas as minhas páginas permanecerão idênticas para todos os usuários, para que eu possa compartilhar seu conteúdo com segurança.
Para fazer isso, minha página emite um:
Cache-Control: public, no-cache, must-revalidate
ETag: W/"xxx"
Ao testar em um navegador, funciona muito bem: a página permanece no cache e simplesmente é revalidada no servidor toda vez que atualizo a página, obtendo 304 na maioria das vezes ou 200 quando altero a versão do conteúdo.
Tudo que preciso agora é compartilhar esse cache entre clientes. Essencialmente:
- Fase A
- O cliente A envia uma solicitação ao proxy
- O proxy não tem cache, então pergunta ao back-end
- Backend responde 200 com uma ETag
- O proxy responde 200 com uma ETag
- Fase B
- O cliente B envia a mesma solicitação ao proxy
- O proxy está em cache, mas deve revalidar (porque não há cache, deve-revalidar e ETag)
- O back-end responde com 304 (porque a solicitação de revalidação inclui o cabeçalho If-None-Match com a ETag armazenada em cache)
- Proxy responde 200 com um Etag
- Fase C
- O cliente A envia a mesma solicitação novamente, desta vez com If-None-Match
- O proxy solicita ao back-end o cabeçalho If-None-Match fornecido (não o armazenado em cache)
- O servidor back-end responde 304
- O proxy responde 304
Eu tentei o nginx, mas requer muitos ajustes para fazê-lo funcionar remotamente. Então experimentei o Traefik antes de perceber que o middleware de cache faz parte da versão corporativa. Então percebi que o Varnish parece implementar o que eu quero.
Então aqui vou eu com minha configuração do Varnish:
vcl 4.0;
backend default {
.host = "localhost";
.port = "3000";
}
backend api {
.host = "localhost";
.port = "8000";
}
sub vcl_recv {
if (req.url ~ "^/back/" || req.url ~ "^/_/") {
set req.backend_hint = api;
} else {
set req.backend_hint = default;
}
}
E claro... Não funcionou.
Ao variar os Cache-Control
cabeçalhos, obtenho o resultado de um cache compartilhado, mas que não é revalidado, ou apenas uma passagem para o cliente, mas nunca parece manter o conteúdo em cache como gostaria.
O que estou faltando para implementar essa lógica de revalidação de cache/ETag compartilhada? Suponho que estou perdendo algo óbvio, mas não consigo descobrir o quê.
Definir um TTL
Conforme declarado na VCL integrada : Varnish trata a
no-cache
diretiva da mesma maneira queprivate
&no-store
: o conteúdo não irá parar no cache.Para a revalidação baseada em ETag isso representa um problema, porque não há nada para comparar.
Meu conselho seria definir um TTL baixo para garantir que ele acabe no cache. Eu recomendaria usar o seguinte
Cache-Control
cabeçalho:Faça com que o Varnish entenda que é necessário revalidar
Outra questão é que o Varnish não entende a
must-revalidate
diretiva, embora a apoiestale-while-revalidate
.Adicionar
must-revalidate
tem o mesmo efeito questale-while-revalidate=0
: não podemos servir conteúdo obsoleto quando o objeto expira e exigir validação síncrona imediata.Isso define o
grace
temporizador interno para zero.Porém, com o seguinte código VCL, você pode fazer com que o Varnish respeite a
must-revalidate
diretiva:Aumente o tempo de manutenção
Ao definir o valor de carência como zero e atribuir um TTL baixo, os objetos expirarão rapidamente e não permanecerão disponíveis por tempo suficiente para revalidação.
Conforme explicado em https://stackoverflow.com/questions/68177623/varnish-default-grace-behavior/68207764#68207764 , o Varnish possui um conjunto de temporizadores que usa para decidir sobre expiração, revalidação e obsolescência tolerada.
Minha sugestão seria aumentar o
keep
timer no VCL. Esse cronômetro garante que objetos expirados e fora de uso sejam mantidos, sem correr o risco de veicular conteúdo desatualizado.A única razão pela qual o
keep
cronômetro existe é para revalidação de ETag, então é exatamente disso que você precisa.Sugiro usar a seguinte VCL para suportar
must-revalidate
e aumentar okeep
cronômetro:Esse snippet aumenta o
keep
cronômetro para um dia, permitindo que o conteúdo expirado permaneça no cache por um dia enquanto ocorre a revalidação.Cuidado com cookies (e cabeçalhos de autorização)
Apesar de ter todos os recursos necessários para oferecer suporte à revalidação de ETag e armazenar objetos no cache, é importante que as solicitações relevantes não ignorem o cache. Isso não tem nada a ver com
Cache-Control
cabeçalhos, mas com cabeçalhos de solicitação.Se você der uma olhada na VCL integrada para a
vcl_recv
sub-rotina , notará que, por padrão, o Varnish ignora o cache para solicitações que contêm umAuthorization
cabeçalho ouCookie
cabeçalho.Se o seu site usa cookies, leia o tutorial a seguir para saber como remover cookies de rastreamento que podem prejudicar sua taxa de acertos: https://www.varnish-software.com/developers/tutorials/removing-cookies-varnish/
Caso de teste de verniz como prova de conceito
Armazene o seguinte conteúdo VTC
etag.vtc
e executevarnishtest etag.vtc
para validar o caso de teste:Conclusão
Se você seguir as etapas que descrevi, sua configuração do Varnish suportará a revalidação de ETag de duas maneiras:
keep
cronômetroConsidere também aumentar o TTL do cache se o seu conteúdo permitir. Tudo isso depende de quantas páginas/recursos mudam.
Por favor, perceba também que manter os objetos por mais tempo (devido a um
keep
valor aumentado) irá preencher o cache. Quando o cache está cheio, uma estratégia LRU é aplicada para remover o objeto menos popular do cache.Se
set beresp.keep = 1d;
for muito e o Varnish começar a remover objetos do cache porque está cheio, considere diminuir okeep
valor.