DevGuideTK

CDN

Servidores perto do usuário para entregar arquivos e respostas sem chegar à sua aplicação.

O código desta página foi executado nas cinco linguagens, cada versão atrás de um Nginx 1.31 configurado como borda de cache. As medições do nível Conceito usaram a mesma borda.

A origem com os cabeçalhos certos, em cada linguagem

Cada versão tem duas rotas. /produtos/7 é pública: leva um Cache-Control que deixa a borda guardar por 5 minutos e o navegador por 1, aceita a cópia vencida durante a revalidação e se a origem cair, e tem um ETag. Quando o pedido traz If-None-Match com o mesmo ETag, a resposta é 304, sem corpo. /minha-conta devolve dados do usuário e proíbe qualquer cache compartilhado.

import crypto from "node:crypto";
import http from "node:http";

// Dado público: a CDN guarda por 5 minutos, o navegador por 1, e ambos aceitam
// a cópia vencida enquanto revalidam ou se a origem cair
const CACHE_PUBLICO =
  "public, max-age=60, s-maxage=300, stale-while-revalidate=60, stale-if-error=86400";

http.createServer((req, res) => {
  if (req.url === "/produtos/7") {
    const corpo = JSON.stringify({ id: 7, nome: "Tênis de corrida", preco: "19.90" });
    const etag = `"${crypto.createHash("sha256").update(corpo).digest("hex").slice(0, 16)}"`;
    res.setHeader("Cache-Control", CACHE_PUBLICO);
    res.setHeader("ETag", etag);
    if (req.headers["if-none-match"] === etag) {
      res.statusCode = 304; // a cópia de quem pediu continua valendo: sem corpo
      return res.end();
    }
    res.setHeader("Content-Type", "application/json");
    return res.end(corpo);
  }
  if (req.url === "/minha-conta") {
    // Dado de um usuário: nenhum cache compartilhado pode guardar
    res.setHeader("Cache-Control", "private, no-store");
    res.setHeader("Content-Type", "application/json");
    return res.end(JSON.stringify({ usuario: req.headers["x-usuario"] }));
  }
  res.statusCode = 404;
  res.end();
}).listen(3000);

O resultado, nas cinco linguagens

PedidoEsperadoNas cinco
/produtos/7, primeira vez, pela bordaMISSMISS
/produtos/7, segunda vez, pela bordaHITHIT
/produtos/7 na origem com If-None-Match304304
/minha-conta da Ana e depois do Bruno, pela bordaCada um vê a suaana e bruno

As cinco versões produziram exatamente o mesmo corpo e o mesmo ETag ("e5f79621d52f5e5a"). Isso tem efeito prático: se várias instâncias, ou serviços em linguagens diferentes, atendem a mesma rota, o ETag precisa sair igual em todas, ou a revalidação falha e a resposta inteira volta a cada vez.

O que muda de uma linguagem para outra

ETag a partir do corpoMesmo JSON, byte a byte
TypeScriptcrypto.createHash("sha256")JSON.stringify já produz o formato compacto
Pythonhashlib.sha256json.dumps com separators=(",", ":") e ensure_ascii=False; o padrão põe espaços e escapa o "ê"
Gosha256.Sum256json.Marshal ordena as chaves de um mapa em ordem alfabética
JavaMessageDigestO JSON montado à mão, só para o exemplo
C#SHA256.HashDataJavaScriptEncoder.UnsafeRelaxedJsonEscaping; o padrão escapa o "ê" como ê

O "mesmo JSON" exigiu cuidado em três linguagens: sem os ajustes, o Python e o C# escapariam o acento, e o ETag sairia diferente para o mesmo dado. Em vez de calcular o ETag do corpo, muitos sistemas usam a versão do registro (updated_at ou um contador), que é igual em qualquer linguagem e evita montar o corpo para responder 304.

Alguns frameworks têm atalhos: o Express gera um ETag por padrão e oferece req.fresh para responder 304, e o Spring tem o ShallowEtagHeaderFilter. O cuidado continua o mesmo: o Cache-Control certo em cada rota.

A borda do teste

A configuração do Nginx que fez o papel da CDN:

proxy_cache_path /tmp/cache keys_zone=borda:10m max_size=100m inactive=1h;

server {
  listen 80;
  location / {
    proxy_pass http://origem:3000;
    proxy_cache borda;
    proxy_cache_key "$request_uri";
    proxy_cache_use_stale error timeout http_502 http_503;  # cópia vencida se a origem cair
    add_header X-Cache $upstream_cache_status always;       # HIT, MISS, STALE
  }
}

O cabeçalho X-Cache mostra o que aconteceu em cada pedido; as CDNs comerciais têm equivalentes (CF-Cache-Status na Cloudflare, X-Cache no CloudFront). Ele é a primeira coisa a olhar quando o cache não acerta.

Lista de revisão

Cabeçalhos

  • Toda resposta tem um Cache-Control explícito, definido por rota?
  • Rotas com dados do usuário têm private, no-store, com teste automatizado?
  • Páginas e APIs públicas têm s-maxage, stale-while-revalidate e stale-if-error?
  • Arquivos estáticos têm nome versionado, cache longo e immutable?
  • As respostas públicas têm ETag, igual em todas as instâncias?

Acertos de cache

  • Páginas públicas saem sem Set-Cookie?
  • Parâmetros de rastreamento estão fora da chave do cache?
  • O Vary tem só o necessário?
  • A taxa de acerto da CDN é acompanhada, e o X-Cache (ou equivalente) é conferido quando cai?

Invalidação

  • Há um caminho para limpar o cache quando o conteúdo muda, por caminho ou por etiqueta?
  • O prazo do cache é aceitável se a limpeza falhar?

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Definir os cabeçalhos de cache na aplicação

    Quero que a minha aplicação envie os cabeçalhos de cache certos em cada rota.
    
    Pergunte qual linguagem e framework eu uso e quais rotas existem.
    
    Vamos em etapas:
    1. Uma forma central de declarar o cache de cada rota.
    2. Cache-Control para estáticos, páginas públicas, APIs públicas e rotas autenticadas.
    3. ETag e respostas 304.
    4. Remover sessões e cookies das respostas públicas.
    5. Um teste que confere o Cache-Control de cada rota, em especial as autenticadas.
  • Montar uma borda local para testar o cache

    Quero testar o comportamento de cache da minha aplicação localmente, antes de colocar uma CDN.
    
    Pergunte qual linguagem eu uso e como a aplicação sobe.
    
    Me ajude a montar:
    1. Um Nginx com proxy_cache na frente da aplicação, mostrando HIT, MISS e STALE num cabeçalho.
    2. Um atraso de rede simulado para a origem.
    3. Testes que confirmam o cache das rotas públicas e a ausência de cache nas autenticadas.
    4. Um teste com a origem desligada.
    5. A comparação com o que a CDN de produção vai fazer.
  • Revisar a configuração de cache e CDN

    Vou colar abaixo os cabeçalhos de cache das rotas da minha aplicação e a configuração da CDN. Revise.
    
    Verifique, e para cada item diga se está correto, incorreto ou ausente, citando o trecho:
    1. Rotas com dados do usuário estão protegidas com private ou no-store?
    2. Páginas e APIs públicas podem ser guardadas pela CDN, com stale-while-revalidate e stale-if-error?
    3. Há Set-Cookie em respostas públicas?
    4. A chave do cache ignora parâmetros de rastreamento?
    5. Estáticos têm nome versionado e cache longo?
    6. Há um caminho de invalidação?
    
    Para cada problema, descreva o efeito em produção e mostre a correção.
    
    Cabeçalhos e configuração:
    [cole aqui]