Montar um servidor CDN próprio: buscar uma vez, entregar mil vezes

Publicado a 9 min de leitura

Um servidor CDN vai buscar cada ficheiro uma vez à origem e entrega-o depois mil vezes por si próprio. O servidor de origem passa a ver apenas uma fração dos pedidos, e os visitantes têm tempos de carregamento curtos. Veja como se monta um.

Cada ficheiro que um visitante carrega da sua página é lido pelo seu servidor de origem no sistema de ficheiros, empacotado numa resposta e enviado. Com dez visitantes por dia, ninguém nota. Com dez mil visitantes em simultâneo, é exatamente essa a razão por que a página fica lenta, apesar de a própria aplicação não ter nada que fazer.

Um servidor CDN coloca-se à frente. Vai buscar cada ficheiro uma vez à origem e entrega-o depois por si próprio, tantas vezes quantas forem necessárias. A origem passa a ver apenas uma fração dos pedidos, e os visitantes recebem os seus ficheiros de um servidor que não faz nada além de entregar. Este artigo mostra quanto isso rende realmente, como se monta um servidor destes com nginx e o que conta no tráfego e na localização.

O que um servidor CDN faz realmente

O processo conta-se em três frases:

  • Um visitante pede um ficheiro ao servidor CDN.
  • Se já está lá, sai imediatamente. É um acerto.
  • Se não está lá, o servidor CDN vai buscá-lo à origem, guarda-o e entrega-o. É uma falha, e acontece por ficheiro apenas uma vez por período de validade.

O princípio é o mesmo de um servidor de streaming: buscar uma vez, entregar muitas vezes. A diferença está apenas em que um servidor de streaming distribui um fluxo de dados em curso e um servidor CDN distribui ficheiros prontos.

A conta: quanto alivia uma cache

Decisiva é a taxa de acertos, ou seja, a parte dos pedidos que a própria cache responde. Com um milhão de pedidos por dia, o alívio tem este aspeto:

Taxa de acertosPedidos na origemAlívio
50 %500 000fator 2
80 %200 000fator 5
95 %50 000fator 20
99 %10 000fator 100

Em ficheiros estáticos, 95 por cento e mais é o caso normal, porque imagens, scripts e tipos de letra mudam raramente. O salto de 95 para 99 por cento é aí mais valioso do que os números deixam supor: volta a dividir por cinco a carga que resta.

Ao mesmo tempo, o tráfego desloca-se. A origem quase não transfere nada, e o servidor CDN tanto mais. Com 500 000 acessos a imagens por dia, a 400 KB cada, são 200 GB diários, ou seja, cerca de 6 TB por mês, só em imagens. Por isso, num nó CDN, o número importante não é a CPU, mas a ligação e o volume.

Quando vale a pena um servidor CDN próprio

Os fornecedores comerciais de CDN cobram por gigabyte transferido. Isso é cómodo e, com pouco volume, barato. A partir de uma certa quantidade, a relação inverte-se:

CasoServidor CDN próprioFaturação por gigabyte
Poucos GB por mêsquase não vale a penamais barato
Muitos TB por mêspreço mensal fixocresce linearmente
Downloads grandes, arquivos de vídeoclaramente em vantagemfica depressa caro
Controlo total sobre regras e registoscompletolimitado

A isto junta-se um ponto difícil de quantificar: num nó próprio, é você que decide o que vai para a cache, por quanto tempo e quando desaparece. Não está preso às regras de um fornecedor alheio e pode entregar também coisas que não encaixam em esquemas de terceiros.

Montagem com nginx como cache

Instalar

apt update
apt install -y nginx

Não é preciso mais nada, a cache está integrada no nginx. Crie o diretório onde os ficheiros devem ficar:

mkdir -p /var/cache/cdn
chown www-data:www-data /var/cache/cdn

Configurar

A cache é descrita uma vez no bloco http e depois usada em cada local:

proxy_cache_path /var/cache/cdn
                 levels=1:2
                 keys_zone=cdn:100m
                 max_size=200g
                 inactive=30d
                 use_temp_path=off;

proxy_cache_key "$scheme$request_method$host$request_uri";

Os valores significam o seguinte: keys_zone=cdn:100m dá para cerca de 800 000 entradas, max_size limita o espaço no disco, inactive=30d deita fora os ficheiros que ninguém pediu durante 30 dias, e use_temp_path=off evita uma movimentação desnecessária entre diretórios.

Abaixo, o servidor propriamente dito:

server {
    listen 443 ssl;
    http2 on;
    server_name cdn.example.com;

    ssl_certificate     /etc/letsencrypt/live/cdn.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cdn.example.com/privkey.pem;

    location / {
        proxy_pass https://origin.example.com;
        proxy_set_header Host origin.example.com;
        proxy_http_version 1.1;

        proxy_cache cdn;
        proxy_cache_valid 200 301 302 24h;
        proxy_cache_valid 404 1m;

        # juntar num só pedido várias falhas simultâneas
        proxy_cache_lock on;
        proxy_cache_lock_timeout 10s;

        # continuar a entregar quando a origem tem problemas
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update on;

        # os cookies de sessão não podem rebentar a cache
        proxy_ignore_headers Set-Cookie;
        proxy_hide_header Set-Cookie;

        add_header X-Cache-Status $upstream_cache_status always;
    }
}

Três linhas daqui merecem atenção especial. O proxy_cache_lock faz com que, num ficheiro novo, não sejam cem visitantes a pedir a origem ao mesmo tempo, mas apenas um, enquanto os restantes esperam. O proxy_cache_use_stale mantém a entrega a funcionar enquanto a origem reinicia ou responde devagar. E esconder o Set-Cookie evita que uma única resposta com cookie de sessão torne a cache inteira inútil.

Verificar se funciona

nginx -t && systemctl reload nginx
curl -sI https://cdn.example.com/bild.jpg | grep -i x-cache-status
curl -sI https://cdn.example.com/bild.jpg | grep -i x-cache-status

No primeiro acesso aparece lá MISS, no segundo HIT. Se também na segunda vez aparecer MISS, a causa está quase sempre num dos três suspeitos habituais: um Set-Cookie na resposta, um Cache-Control: no-store vindo da origem ou uma indicação de comprimento que falta.

O que vai para a cache e o que não vai

A solução limpa é a própria origem dizê-lo. Ela conhece os seus conteúdos, a cache não. Para isso define os cabeçalhos adequados:

ConteúdoCabeçalho na origem
Ficheiro com soma de verificação no nomeCache-Control: public, max-age=31536000, immutable
Imagem ou download sem versionamentoCache-Control: public, max-age=86400
HTML de uma página públicaCache-Control: public, max-age=300
Área autenticada, carrinho de comprasCache-Control: private, no-store

Se quiser decidir na própria cache, isso faz-se com locais próprios por tipo de ficheiro:

location ~* \.(jpg|jpeg|png|gif|webp|avif|svg|ico|css|js|woff2|mp4|zip)$ {
    proxy_pass https://origin.example.com;
    proxy_cache cdn;
    proxy_cache_valid 200 7d;
    add_header Cache-Control "public, max-age=604800";
    add_header X-Cache-Status $upstream_cache_status always;
}

Livrar-se de conteúdos de forma direcionada

O caminho mais simples é não ter de apagar nada: quem dá um nome de ficheiro novo a cada imagem alterada, por exemplo logo.a1b2c3.png, não tem o problema. O ficheiro antigo acaba por cair da cache sozinho.

Se isso não for possível, no nginx ajuda o módulo livre ngx_cache_purge. Sem módulo adicional, o caminho pode ser calculado a partir da chave de cache, porque o nginx usa a soma MD5 dessa chave como nome de ficheiro:

KEY="httpsGETcdn.example.com/bild.jpg"
H=$(printf '%s' "$KEY" | md5sum | cut -d' ' -f1)
rm -f "/var/cache/cdn/${H: -1}/${H: -3:2}/$H"

Para o dia a dia basta normalmente um pequeno script que execute estas linhas para uma lista de caminhos.

Ficheiros grandes e vídeos

Em ficheiros acima de algumas centenas de megabytes precisa da divisão em blocos, caso contrário a cache volta a carregar o ficheiro inteiro a cada salto no vídeo:

location /video/ {
    proxy_pass https://origin.example.com;
    proxy_cache cdn;
    proxy_cache_valid 200 206 7d;

    slice 1m;
    proxy_set_header Range $slice_range;
    proxy_cache_key "$uri$slice_range";
}

Assim o nginx vai buscar apenas os blocos que são realmente pedidos. Se um espectador salta para o meio de um filme, passam poucos megabytes pela linha em vez de vários gigabytes.

TLS, HTTP/2 e HTTP/3

Um nó CDN entrega muitos ficheiros pequenos a muitas ligações, e é precisamente aí que os protocolos mais recentes compensam. O HTTP/2 fica resolvido com http2 on;. O HTTP/3 traz vantagens sobretudo em ligações móveis, porque uma mudança de rede não faz cair a ligação:

listen 443 quic reuseport;
listen 443 ssl;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400';

Não se esqueça de abrir o UDP 443 na firewall. É a razão mais frequente para o HTTP/3 estar configurado e, apesar disso, ninguém o usar.

Vários locais

Um único nó já traz a maior parte do ganho, ou seja, o alívio da origem. O segundo ganho, caminhos curtos até ao visitante, só chega com vários locais.

A distribuição corre por DNS: o mesmo nome aponta para um nó diferente conforme a proveniência do visitante. Para isso precisa de um fornecedor de DNS com atribuição geográfica. Todos os nós recebem a mesma configuração e a mesma origem, não sabem nada uns dos outros, e é precisamente isso que torna a montagem tão robusta: se um nó falha, tira-o do DNS e os restantes assumem.

A KernelHost oferece locais na Alemanha, na costa oeste dos EUA, na costa leste dos EUA, bem como em Singapura e na Coreia do Sul. Encontra uma visão geral com latências em Localizações de servidores. Como regra prática: um nó por cada continente em que esteja uma parte considerável do seu público.

Medir em vez de adivinhar

Escreva o estado da cache no registo, assim pode contar a taxa de acertos em qualquer momento:

log_format cdn '$remote_addr $status $body_bytes_sent '
               '$upstream_cache_status "$request"';
access_log /var/log/nginx/cdn.log cdn;

A avaliação faz-se com uma única linha:

awk '{print $4}' /var/log/nginx/cdn.log | sort | uniq -c | sort -rn

Se a parte de HIT em ficheiros estáticos ficar abaixo de 90 por cento, procurar a causa compensa quase sempre: normalmente são cookies nas respostas, validades demasiado curtas ou pedidos com parâmetros variáveis no fim do endereço, que para a cache parecem de cada vez um ficheiro novo.

Que servidor serve

Um nó CDN é uma máquina de entrega. A ordem das propriedades importantes é por isso diferente da de um servidor de aplicação:

  1. Tráfego e ligação. É aqui que nasce toda a carga. Em funcionamento, um nó transfere regularmente vários terabytes por mês.
  2. Disco. O NVMe está claramente em vantagem, porque a cache consiste em muitos acessos pequenos e aleatórios. O tamanho orienta-se pela parte do seu acervo que está quente, não pelo tamanho total do acervo.
  3. RAM. O sistema operativo usa a memória livre como segunda cache à frente do disco. Aqui, mais RAM significa diretamente mais velocidade.
  4. CPU. O menos crítico, só é precisa para TLS, e isso os processadores modernos fazem de passagem.

É exatamente para este caso que os nossos servidores root com tráfego ilimitado foram feitos: 2 × 1 Gbit/s reais por servidor sem estrangulamento, volume ilimitado a preço mensal fixo, NVMe de datacenter em RAID e 3,2 Tbps de proteção contra floods de fábrica. A disponibilização leva menos de 30 segundos, com acesso root completo, IPv4 e uma rede IPv6 /64. Como a faturação é em regime PrePaid e cancelável mensalmente, pode começar com um nó e acrescentar a qualquer momento um segundo local, quando o seu público se distribuir.

Perguntas frequentes

O que faz exatamente um servidor CDN?
Fica entre os seus visitantes e o seu servidor de origem. O primeiro pedido de um ficheiro é encaminhado para a origem, e a resposta é guardada nesse momento. Todos os pedidos seguintes do mesmo ficheiro são respondidos por ele próprio, sem incomodar a origem.
Quanto alivia isto realmente?
Depende da taxa de acertos. Com 95 por cento, a sua origem vê apenas um de cada vinte pedidos, pelo que a carga cai para um vigésimo. Com 80 por cento, continua a ser um quinto. Para ficheiros estáticos como imagens, scripts, tipos de letra e downloads, 95 por cento e mais é o normal.
Preciso de um fornecedor comercial para isto?
Não necessariamente. Um servidor CDN próprio compensa sobretudo com ficheiros grandes e muito volume, porque os fornecedores comerciais cobram por gigabyte transferido. Num servidor com tráfego ilimitado, o centésimo terabyte custa o mesmo que o primeiro, ou seja, nada mais.
Que ficheiros devo colocar em cache e quais não?
Tudo o que é igual para todos os visitantes: imagens, vídeos, CSS, JavaScript, tipos de letra, PDF, ficheiros de instalação. Não deve colocar em cache páginas personalizadas, carrinhos de compras, áreas de início de sessão e tudo o que leve um cookie de sessão. Essa decisão é melhor tomada pela origem, através do cabeçalho Cache-Control.
Quanto tempo deve a cache guardar os ficheiros?
Em ficheiros com número de versão ou soma de verificação no nome, um ano sem problemas, porque nunca mudam. Em imagens e downloads sem versionamento, é habitual de horas a dias. Em HTML, se é que o coloca em cache, minutos. O importante não é o número exato, mas ter uma forma de apagar ficheiros individuais de forma direcionada.
Como apago um ficheiro da cache?
O mais simples é pelo nome do ficheiro: quem atribui um nome novo a cada alteração não precisa de apagar nada. Se isso não for possível, no nginx ajuda o módulo comercial de purge ou o livre ngx_cache_purge. Em último recurso, apaga o ficheiro de cache diretamente no sistema de ficheiros, já que o caminho pode ser calculado a partir da chave de cache.
O que acontece se o servidor de origem falhar?
Bem configurada, a cache continua então a entregar, mesmo que as entradas já estejam expiradas. Disso tratam o proxy_cache_use_stale com error e timeout, bem como o proxy_cache_background_update. A sua página mantém-se assim acessível enquanto a origem está em manutenção ou a reiniciar.
Isto também funciona para ficheiros de vídeo grandes?
Sim, mas só com a configuração de slice. Sem ela, a cache volta a carregar o ficheiro inteiro da origem a cada salto no vídeo. Com o slice, o nginx divide o ficheiro em blocos de, por exemplo, um megabyte e guarda apenas as partes realmente pedidas.
De quantos locais preciso?
Comece com um, e precisamente onde está a maioria dos seus visitantes. Um segundo local só compensa quando uma parte considerável do público está noutro continente. A distribuição corre então por DNS, e cada visitante acaba no nó mais próximo.
Como vejo que a cache está a funcionar?
Mostre a variável upstream_cache_status como cabeçalho. No segundo acesso ao mesmo ficheiro tem de aparecer lá HIT. Se escrever também esse valor no registo, pode contar a taxa de acertos em qualquer momento. Abaixo de 90 por cento em ficheiros estáticos, normalmente há algo errado com os cabeçalhos ou com os cookies.

CDN Caching nginx Reverse Proxy Performance HTTP Unlimited Traffic