O que um CDN realmente faz com uma requisição

Um usuário em Tóquio que carrega uma página hospedada num servidor na Virgínia paga essa distância duas vezes: uma vez para a requisição chegar, outra para a resposta voltar. A fibra óptica corre numa fração fixa da velocidade da luz, e só essa viagem já custa algo entre 150 e 200ms de round-trip antes de a origem fazer qualquer trabalho. Some uma query no banco de dados e mais um ou dois saltos lentos. Uma página que parece instantânea na porta ao lado fica arrastada do outro lado do planeta.

Um CDN, ou content delivery network, resolve isso para o conteúdo que pode ser copiado: imagens, CSS, bundles JavaScript, segmentos de vídeo, às vezes páginas HTML inteiras. Uma rede de servidores posicionados perto dos usuários guarda cópias desse conteúdo e responde diretamente, então a maioria das requisições nunca atravessa o oceano. A origem continua sendo a versão de referência. O CDN fica na frente dela e intercepta o máximo de requisições que conseguir responder com uma cópia.

Como uma requisição escolhe qual servidor responde

Um operador de CDN mantém servidores em muitos locais físicos, geralmente chamados de Points of Presence, ou PoPs: data centers em ou perto de grandes cidades, cada um guardando uma fatia do conteúdo em cache e um caminho rápido de volta até a origem para o resto. Dois mecanismos decidem qual PoP atende uma determinada requisição, e os grandes CDNs usam os dois.

O roteamento baseado em DNS resolve o mesmo hostname para IPs diferentes dependendo de onde a consulta vem. Uma consulta DNS para assets.example.com feita por um resolver em Frankfurt recebe de volta o IP de um PoP em Frankfurt; a mesma consulta vinda de São Paulo recebe um IP totalmente diferente.

O anycast vai além. Vários PoPs anunciam o mesmo endereço IP via BGP, e o roteamento normal da internet decide qual deles um determinado pacote realmente alcança, com base na topologia da rede em vez de na geografia. O CDN nunca faz essa escolha. O failover também é mais rápido: quando um PoP cai da rede, os roteadores param de enviar tráfego para ele, sem nenhuma mudança de DNS para propagar.

1
2
3
4
5
6
cliente (Tóquio)  ---DNS/anycast--->  PoP mais próximo  ---cache hit--->  resposta
                                            |
                                      cache miss
                                            |
                                            v
                                     origem (Virgínia)

De um jeito ou de outro, o cliente nunca fala diretamente com a origem para conteúdo em cache. Ele fala com o PoP que a camada de roteamento escolheu, e esse PoP já tem o conteúdo ou busca uma vez e guarda. Cada PoP faz o trabalho de um reverse proxy na frente da origem, encerrando a conexão do cliente e decidindo o que acontece a seguir, com um cache acoplado e milhares de cópias rodando ao mesmo tempo pelo mundo todo.

Cache hit, cache miss: como saber qual você recebeu

A primeira requisição para uma URL através de um determinado PoP é um cache miss. O PoP não tem nada guardado, então busca na origem, guarda uma cópia de acordo com as regras da resposta, e a retorna. Toda requisição seguinte para essa URL, de qualquer cliente roteado para o mesmo PoP, é um hit: servida do armazenamento do PoP, sem envolver a origem, até a cópia expirar ou ser removida.

Um comando diz qual dos dois você recebeu:

1
curl -I https://assets.example.com/app.css

Uma resposta em cache carrega um header proprietário que nomeia o resultado. O Cloudflare adiciona CF-Cache-Status com valores como HIT, MISS, EXPIRED (o TTL acabou, então o PoP buscou de novo), REVALIDATED (a origem confirmou que a cópia antiga ainda era válida) ou DYNAMIC (o CDN decidiu que essa resposta não é cacheável). O equivalente da Fastly é X-Cache: HIT ou X-Cache: MISS. Rode a mesma requisição duas vezes numa URL cacheável e a segunda deve virar de MISS para HIT.

1
2
3
4
5
HTTP/2 200
cache-control: public, max-age=3600
cf-cache-status: HIT
age: 412
content-type: text/css

age é há quantos segundos o PoP buscou essa cópia na origem. Aqui isso é 412 segundos dentro de um TTL de 3600 segundos, então a cópia ainda tem quase uma hora antes de a próxima requisição disparar uma busca nova.

Como o Cache-Control diz ao CDN o que guardar

O CDN não adivinha o que deve guardar em cache. A origem informa isso através do header Cache-Control em cada resposta.

DiretivaO que faz
max-age=NPor quanto tempo qualquer cache — navegador ou CDN — pode guardar a resposta, em segundos
s-maxage=NPor quanto tempo um cache compartilhado (o CDN) pode guardá-la — sobrepõe o max-age especificamente para o CDN; os navegadores continuam seguindo o max-age
publicPermite o cache mesmo em respostas que pareceriam privadas (por exemplo, com um header Authorization)
privateSó o navegador pode guardar isso em cache; o CDN não pode
no-cacheOs caches podem guardar, mas precisam revalidar com a origem antes de servir de novo
no-storeNunca guardar isso em cache em lugar nenhum, nem navegador nem CDN

Para um asset estático com nome de arquivo com hash (app.a3f9c1.css), a configuração normal é agressiva: Cache-Control: public, max-age=31536000, immutable. O nome do arquivo muda sempre que o conteúdo muda, então um ano é seguro. Uma cópia desatualizada de app.a3f9c1.css é uma contradição, porque esse nome exato sempre aponta para uma única versão do arquivo.

Para HTML ou uma resposta de API que você quer em cache mas mais atualizada, Cache-Control: public, s-maxage=60, max-age=0 mantém isso no CDN por um minuto enquanto os navegadores revalidam a cada vez. É o mesmo instinto de TTL curto e cache-aside por trás de como funciona o cache Redis, só que aplicado por um header HTTP em vez de código da aplicação.

Errar isso na direção contrária vaza dados. Servir uma página específica de um usuário com Cache-Control: public, max-age=3600 e sem Vary no cookie de sessão significa que o CDN pode entregar a página em cache do usuário A para o usuário B.

Como limpar um cache de CDN desatualizado

Às vezes um TTL não é suficiente: você lançou uma correção e não pode esperar uma hora até ela chegar a todo mundo. Todo CDN expõe uma API de purge ou uma ação no painel para isso. Diga a ele que uma URL, ou uma zona inteira, ficou inválida agora, e a próxima requisição é tratada como cache miss não importa quanto TTL ainda reste.

O hábito mais barato é nunca precisar dar purge. Coloque um hash de conteúdo no nome do arquivo, ou incremente uma query string a cada deploy, para que cada versão vire uma URL nova com sua própria entrada de cache — a cópia desatualizada da URL antiga deixa de ser requisitada. Reserve o purge para o que não dá para contornar: um incidente, uma remoção por exigência legal, um cache envenenado por um header Vary mal configurado.

Quando um CDN não ajuda, e o que isso custa

Respostas personalizadas não são cacheáveis de nenhum jeito útil. Um dashboard logado, uma página de carrinho, qualquer coisa amarrada a uma sessão — a próxima requisição vai querer um conteúdo diferente, garantido. Apontar um CDN para esses endpoints não traz nada além de uma falsa sensação de ter adicionado cache, e se as regras de cache estiverem erradas, ainda corre o risco real de servir a resposta privada de um usuário para outro.

Um CDN também acrescenta uma camada a mais para depurar. “Funciona local mas não em produção” às vezes significa que o código está correto e um PoP está servindo uma resposta guardada em cache de antes da sua correção; a requisição só chega na origem quando o TTL acaba ou você faz o purge.

O formato do tráfego também importa. Uma ferramenta interna, ou um serviço regional sem público internacional, paga por PoPs que nunca usa e recupera uma latência que nunca teve. Um CDN resolve dois problemas específicos: a viagem até usuários distantes e a carga repetida na origem. Se nenhum dos dois é o seu problema, é infraestrutura sem nada para fazer.

Como saber se você já tem um CDN

Rode curl -I contra um asset estático num site que você mantém. Se a resposta carrega cache-control, um header age, ou um status proprietário como CF-Cache-Status ou X-Cache, alguma coisa já está fazendo cache na frente da sua origem, seja porque você configurou de propósito ou porque seu provedor de hospedagem já vem com isso embutido. Se esses headers estiverem ausentes em assets pedidos de mais de uma região, esse é o caso concreto para adicionar um: Cloudflare, AWS CloudFront e Fastly colocam um domínio atrás deles num plano gratuito ou por uso em menos de uma hora, sem precisar mudar o código da origem.

Artigos relacionados