Voltar ao Blog

Dimensionamento de CDN para Game Assets: Um Runbook para Sobreviver a Picos de Tráfego no Dia do Lançamento

Publicado em 4 de agosto de 2026
Dimensionamento de CDN para Game Assets: Um Runbook para Sobreviver a Picos de Tráfego no Dia do Lançamento Gerada com a ajuda de IA

Em resumo

Aprenda a dimensionar CDN para game assets e sobreviver a picos de tráfego no lançamento com estratégias de cache, origin shielding e failover.

Seu CDN Vai Ceder — Veja Como Saber Quando

Todo desenvolvedor de jogos teme o mesmo cenário de lançamento: sua página na Steam vai ao ar, o número de jogadores passa de 10.000 simultâneos e, de repente, downloads de texturas travam em latência p99 de 200ms em vez dos usuais 12ms. Jogadores relatam modelos ausentes. Downloads de patch travam em 43%. Seu dashboard de monitoramento fica vermelho e você não faz ideia de qual camada está falhando.

Isso não é hipotético. O cdnjs — uma das redes de CDN open-source mais usadas do planeta — concluiu recentemente uma migração completa de infraestrutura para a Developer Platform da Cloudflare para lidar com 9 bilhões de requisições por dia. A história da migração revela padrões arquiteturais que se aplicam diretamente à entrega de game assets, onde uma única atualização de pacote de texturas 4K pode gerar terabytes de tráfego em minutos.

A lição central: dimensionar CDN para game assets não é comprar mais banda. É projetar hierarquias de cache, lógica de fallback e origin shielding para que picos de tráfego se tornem não-eventos em vez de indisponibilidades.

Este runbook aborda o que quebra quando seu CDN satura, como detectar a saturação antes que seu Discord encha de fúria, como remediar em produção e como arquitetar para prevenir recorrências.


O Que Quebra Quando Seu CDN Satura

A entrega de game assets tem um perfil de tráfego único comparado ao conteúdo web padrão. Entender os modos de falha exige entender esse perfil.

O Problema do Formato de Tráfego

Um jogo multiplayer indie típico apresenta estes padrões de tráfego:

  • Linha de base: 50-200 requisições/seg para assets de lobby, sprites de UI, JSON de configuração
  • Pico no dia de patch: 15.000-80.000 requisições/seg em uma janela de 3 minutos quando a Steam dispara atualizações automáticas
  • Cascatas regionais: jogadores da Ásia-Pacífico acessam o CDN 8-12 horas depois dos da América do Norte, criando uma segunda onda
  • Explosão de versões de assets: Cada patch invalida objetos em cache, forçando pulls de origem para novos hashes

Quando o cdnjs migrou para a infraestrutura da Cloudflare, eles enfrentaram um problema semelhante de explosão de versões. O versionamento no estilo npm significava que cada atualização de biblioteca criava novas chaves de cache, e com mais de 4.200 bibliotecas atualizadas diariamente, o design de origin shielding precisava lidar com churn contínuo de cache — não apenas conteúdo estático.

Os Três Modos de Falha

1. Saturação de Origin Pull

Quando o cache de borda (edge cache) falha (patch novo, cache frio, expiração de cache), toda requisição atinge seu servidor origin. Uma única origin com throughput de 1 Gbps consegue atender cerca de 1.250 downloads simultâneos de assets de 1 MB. Com 80.000 jogadores simultâneos cada um baixando um patch de 2 GB, você precisa de capacidade de origin que a maioria dos setups indie simplesmente não tem.

2. Cache Stampede

Quando o asset mais requisitado expira do cache de borda (má configuração de TTL, purge disparado por deploy), milhares de edge nodes solicitam simultaneamente o mesmo objeto da origin. Esse é o problema da "manada trovejante" (thundering herd), e ele derruba origins em segundos.

3. Fome de Edge Regional

Seus edge nodes na América do Norte estão quentes. Seu edge node em Singapura tem uma taxa de cache hit de 60% porque você tem apenas 12.000 jogadores APAC — até um YouTuber no Japão destacar seu jogo e esse número saltar para 300.000 da noite para o dia. O edge node puxa da origin em escala massiva, e jogadores APAC experimentam tempos de carregamento de 2 a 4 segundos enquanto jogadores NA veem 40ms.

Sinais de Detecção

# Cloudflare API: check cache hit ratio by region (run every 60 seconds)
curl -s -X POST "https://api.cloudflare.com/client/v4/graphql" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{
      viewer {
        zones(filter: {zoneTag: \"YOUR_ZONE\"}) {
          httpRequests1hGroups(limit: 24, filter: {date_gt: \"2025-01-01\"}) {
            dimensions { datetime, cacheStatus, clientCountryName }
            sum { requests, bytes }
          }
        }
      }
    }"
  }' | jq '.data.viewer.zones[0].httpRequests1hGroups[] |
    select(.dimensions.cacheStatus == "miss") |
    {region: .dimensions.clientCountryName, misses: .sum.requests}'

Se sua taxa de cache miss exceder 8% em qualquer região durante um período de estado estável, você está a um patch de uma inundação de origin.


Remediação Imediata: O Que Fazer Agora

Quando o CDN está pegando fogo, você tem uma janela de 15 minutos antes de os jogadores começarem a fazer review bombing. Aqui está a sequência de triagem.

Passo 1: Ativar Origin Shielding

A maioria dos provedores de CDN oferece um recurso de "origin shield" ou "shielding" — uma camada intermediária de cache entre seus edge nodes e sua origin. Em vez de 200 edge nodes cada um atingindo a origin de forma independente em um cache miss, apenas o nó shield contata a origin e distribui a resposta.

Exemplo de configuração (API genérica de CDN):

{
  "shielding": {
    "enabled": true,
    "shield_region": "us-east-1",
    "fallback_shield_region": "eu-west-1",
    "shield_ttl_override": 86400,
    "pass_on_shield_error": false
  }
}

Essa única mudança pode reduzir a carga na origin em 95% durante um cache stampede. A migração do cdnjs dependia de lógica de shielding semelhante — os servidores origin deles viram uma redução de milhões de pulls diretos para alguns milhares de requisições originadas pelo shield por hora.

Passo 2: Estender TTLs de Ativos para Conteúdo Estático

Suas texturas 4K, bancos de áudio e arquivos de mesh não mudam entre patches. Não há motivo para um TTL de 1 hora.

# nginx origin server: aggressive caching for immutable game assets
location /assets/v*/ {
    # Version-prefixed paths mean new versions get new URLs
    # No need to purge — old URLs stay cached forever
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header CDN-Cache-Control "max-age=31536000";
}

# Short TTL only for manifest files that change each patch
location /manifest.json {
    add_header Cache-Control "public, max-age=60, stale-while-revalidate=300";
}

A principal percepção da arquitetura do cdnjs: versione seus assets no caminho da URL, não com query strings. Muitos CDN nodes tratam ?v=2 e ?v=3 como a mesma chave de cache. Use /assets/v2/texture_pack.bin em vez disso.

Passo 3: Ativar Stale-While-Revalidate

Esta é a configuração de maior impacto para o tráfego do dia de lançamento. Quando um asset em cache expira, o CDN serve a versão stale ao jogador que fez a requisição enquanto busca a versão nova em segundo plano. O jogador recebe uma resposta de 12ms em vez de uma de 1.200ms.

Cache-Control: public, max-age=3600, stale-while-revalidate=86400

Isso diz ao CDN: "Este asset está fresco por 1 hora. Depois disso, sirva a versão stale por até 24 horas enquanto você revalida em segundo plano."

Para game assets que não são críticos de segurança (fundos de lobby, previews cosméticos, stems de áudio), isso é seguro e reduz drasticamente a latência percebida.

Passo 4: Implementar Fallbacks com Circuit Breaker

Se a origin do CDN está realmente sobrecarregada, seu cliente de jogo precisa de um caminho de degradação graciosa — não uma tela de carregamento congelada.

// C# Unity: CDN circuit breaker with local fallback
public class AssetLoader
{
    private const int MAX_RETRIES = 3;
    private const int TIMEOUT_MS = 5000;
    private static int _failureCount = 0;
    private static DateTime _circuitOpened = DateTime.MinValue;
    private static readonly TimeSpan CIRCUIT_RESET = TimeSpan.FromMinutes(2);

    public async Task<byte[]> LoadAsset(string assetPath)
    {
        // Circuit breaker: skip CDN if recent failures exceeded threshold
        if (_failureCount >= MAX_RETRIES &&
            DateTime.UtcNow - _circuitOpened < CIRCUIT_RESET)
        {
            Debug.LogWarning($"CDN circuit open — loading {assetPath} from local cache");
            return LoadFromLocalStorage(assetPath);
        }

        try
        {
            using var client = new HttpClient { Timeout = TimeSpan.FromMilliseconds(TIMEOUT_MS) };
            var response = await client.GetAsync($"https://cdn.yourgame.com/{assetPath}");
            response.EnsureSuccessStatusCode();
            _failureCount = 0; // Reset on success
            return await response.Content.ReadAsByteArrayAsync();
        }
        catch (Exception ex)
        {
            _failureCount++;
            if (_failureCount >= MAX_RETRIES)
                _circuitOpened = DateTime.UtcNow;

            Debug.LogWarning($"CDN fetch failed ({_failureCount}/{MAX_RETRIES}): {ex.Message}");
            return LoadFromLocalStorage(assetPath);
        }
    }

    private byte[] LoadFromLocalStorage(string assetPath)
    {
        // Ship a minimal "emergency asset pack" with your game binary
        // This covers the 20 most critical assets: UI, default textures, lobby music
        var localPath = Path.Combine(Application.streamingAssetsPath, "fallback", assetPath);
        return File.Exists(localPath) ? File.ReadAllBytes(localPath) : Array.Empty<byte>();
    }
}

Esse padrão garante que seu jogo continue funcional mesmo quando o CDN está completamente fora. Os jogadores podem ver texturas de resolução mais baixa por alguns minutos, mas ainda conseguem jogar.


Prevenção: A Arquitetura de Cache em Múltiplas Camadas

Remediação salva você no dia do lançamento. Arquitetura impede que você precise dela.

O Padrão de Três Camadas

A migração do cdnjs para Cloudflare Workers demonstrou uma arquitetura de cache que escala para bilhões de requisições. Adaptada para game assets:

Camada 1 — Edge Cache (PoPs do CDN)

  • Atende 95-99% das requisições
  • TTL: 365 dias para assets versionados, 60 segundos para manifests
  • Cobre texturas, meshes, áudio, shaders

Camada 2 — Cache Shield / Mid-Tier

  • Intercepta cache misses dos edge nodes
  • TTL: igual ao da edge, mas atua como proxy da origin
  • Reduz a carga na origin em 95%+

Camada 3 — Servidor Origin

  • Gera assets, assina URLs, serve manifests
  • Protegido por rate limiting e shielding
  • Deve ver <0,1% do volume total de tráfego

Pipeline de Assets Versionados

Aqui está o fluxo de versionamento de assets que previne tempestades de invalidação de cache:

# Python: asset pipeline that generates cache-safe versioned URLs
import hashlib
import json
import os

def build_asset_manifest(asset_dir: str, cdn_base: str) -> dict:
    """
    Walk asset directory, hash each file, and produce a manifest
    with versioned URLs that CDN edge nodes can cache forever.
    """
    manifest = {"version": "", "assets": {}}

    for root, _, files in os.walk(asset_dir):
        for filename in sorted(files):
            filepath = os.path.join(root, filename)
            relative_path = os.path.relpath(filepath, asset_dir)

            # Content hash — identical files get identical URLs
            with open(filepath, "rb") as f:
                file_hash = hashlib.sha256(f.read()).hexdigest()[:12]

            # Version in the PATH, not query string
            # CDN treats /assets/a3f9b2c1e8d4/texture.bin as a unique object
            versioned_url = f"{cdn_base}/assets/{file_hash}/{relative_path}"

            manifest["assets"][relative_path] = {
                "url": versioned_url,
                "hash": file_hash,
                "size": os.path.getsize(filepath),
            }

    # Manifest version = hash of the entire asset set
    all_hashes = "".join(
        a["hash"] for a in sorted(manifest["assets"].values(), key=lambda x: x["url"])
    )
    manifest["version"] = hashlib.sha256(all_hashes.encode()).hexdigest()[:16]

    return manifest


# Usage
manifest = build_asset_manifest("./build/assets", "https://cdn.yourgame.com")
with open("./build/manifest.json", "w") as f:
    json.dump(manifest, f, indent=2)

print(f"Manifest version: {manifest['version']}")
print(f"Total assets: {len(manifest['assets'])}")
# Output:
# Manifest version: a8f3e1c92b4d7061
# Total assets: 2,847

Com essa abordagem:

  • Assets antigos nunca são purgados. Eles permanecem em cache na edge indefinidamente porque têm URLs únicas.
  • Assets novos ganham URLs novas. O CDN automaticamente os coloca em cache na primeira requisição.
  • O único arquivo que muda é o manifest. Um pequeno arquivo JSON com TTL de 60 segundos.

É exatamente assim que o cdnjs lida com versionamento de bibliotecas em escala. Cada versão de biblioteca tem um caminho de URL único, então o CDN nunca precisa de operações de purge — a operação de CDN mais cara e propensa a erros que existe.

Esse padrão arquitetural é especialmente importante se você está rodando dedicated servers que precisam servir dados de configuração junto com a lógica do jogo. Como abordamos em nosso guia sobre como dominar o asset stripping de dedicated servers na Unreal Engine, separar assets estáticos de dados críticos do servidor é uma otimização fundamental que se potencializa em escala.


Distribuição Geográfica: Resolvendo a Cascata Regional

A migração do cdnjs revelou que a contagem bruta de edge nodes é menos importante do que roteamento inteligente. Ter 300 PoPs não significa nada se a lógica de roteamento envia requisições APAC para uma origin nos EUA em cache miss.

Seleção Inteligente de Origin

{
  "origin_rules": [
    {
      "name": "us-primary",
      "origin_server": "origin-us.yourgame.com",
      "regions": ["NA", "SA"],
      "health_check": "/health",
      "failover_origin": "origin-eu.yourgame.com"
    },
    {
      "name": "eu-primary",
      "origin_server": "origin-eu.yourgame.com",
      "regions": ["EU", "AF"],
      "health_check": "/health",
      "failover_origin": "origin-us.yourgame.com"
    },
    {
      "name": "apac-primary",
      "origin_server": "origin-apac.yourgame.com",
      "regions": ["AS", "OC"],
      "health_check": "/health",
      "failover_origin": "origin-us.yourgame.com"
    }
  ]
}

Servidores origin regionais custam US$ 20-40/mês cada um nos principais provedores de nuvem. Três origins regionais custam menos do que um único incidente em que sua origin NA atende tráfego APAC por 4 horas com performance degradada — e os jogadores perdidos que vêm junto.

Esse tipo de arquitetura de failover multirregional reflete o que discutimos em nossa análise sobre arquitetando servidores zero-waste com estratégias de hibernação — o princípio de não pagar por infraestrutura ociosa enquanto ainda está pronto para escalar.


Melhores Práticas para Dimensionar CDN de Game Assets

1. Versione assets em caminhos de URL, não em query strings. /assets/{hash}/texture.bin garante unicidade de cache. ?v=2 não garante — muitos CDN nodes removem parâmetros de query das chaves de cache, o que significa conteúdo stale ou caches quebrados.

2. Separe o TTL do manifest do TTL dos assets. Arquivos de manifest devem ter TTL de 30-60 segundos com stale-while-revalidate. Arquivos de asset devem ter TTL de 1 ano com immutable. Essa distinção é a diferença entre um rollout de patch suave e um cache stampede.

3. Envie um pacote de assets de fallback junto com o binário do jogo. Os 50-100 assets mais críticos (UI, skin padrão, ambiente de lobby) devem estar dentro da instalação do jogo como um pacote de emergência de 200-500 MB. Sua lógica de circuit breaker recorre a eles quando o CDN está inacessível.

4. Monitore a taxa de cache hit por região, não globalmente. Uma taxa global de 97% pode mascarar uma taxa de 72% no Sudeste Asiático. Monitoramento por região permite que você identifique fome de edge regional antes que vire um incidente reportado por jogadores.

5. Faça load test do seu CDN antes do lançamento, não durante. Use ferramentas como k6, Locust ou Vegeta para simular seu padrão de tráfego esperado no dia do lançamento contra o endpoint do seu CDN. Um teste de 10 minutos com 50.000 usuários virtuais acessando seu manifest + os 20 assets principais vai revelar TTLs mal configurados, shielding ausente e gargalos de origin antes que os jogadores reais descubram.

# k6: simulate 50,000 concurrent players hitting the asset manifest
cat <<'EOF' > cdn_load_test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 10000 },  // Ramp to 10K VUs
    { duration: '3m', target: 50000 },  // Spike to 50K VUs
    { duration: '5m', target: 50000 },  // Sustain
    { duration: '2m', target: 0 },      // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<200'],   // 95th percentile under 200ms
    http_req_failed: ['rate<0.01'],     // Less than 1% errors
  },
};

export default function () {
  const manifestRes = http.get('https://cdn.yourgame.com/manifest.json');
  check(manifestRes, {
    'manifest status 200': (r) => r.status === 200,
    'manifest under 100ms': (r) => r.timings.duration < 100,
    'cache HIT': (r) => r.headers['Cf-Cache-Status'] === 'HIT',
  });

  // Simulate a player downloading 5 random assets
  for (let i = 0; i < 5; i++) {
    const assetPath = `assets/placeholder_${Math.floor(Math.random() * 100)}/mesh.bin`;
    const assetRes = http.get(`https://cdn.yourgame.com/${assetPath}`);
    check(assetRes, {
      'asset under 500ms': (r) => r.timings.duration < 500,
    });
  }

  sleep(1);
}
EOF

k6 run cdn_load_test.js

Quando Construir Você Mesmo vs. Usar uma Plataforma

Construir toda a arquitetura de cache em múltiplas camadas descrita acima é totalmente viável para um time com engenheiros de infraestrutura dedicados. Os componentes são bem documentados e os provedores de CDN oferecem os primitivos básicos.

Mas se seu time é de três desenvolvedores lançando um jogo, gastar 4-6 semanas construindo origin shielding, failover regional, pipelines de versionamento de assets e lógica de circuit breaker no seu cliente significa 4-6 semanas não gastas em gameplay. horizOn cuida da infraestrutura de entrega de assets como parte do seu backend stack, dando a você o mesmo cache multirregional e failover automático sem o custo operacional. Você envia os assets; a plataforma lida com versionamento, distribuição de edge e monitoramento de saúde out of the box.

Os princípios arquiteturais deste artigo continuam críticos independentemente da sua escolha de infraestrutura. Entender por que caminhos de URL versionados importam, por que stale-while-revalidate previne stampedes e por que origins regionais reduzem latência significa que você pode tomar decisões informadas — esteja você configurando Cloudflare Workers manualmente ou avaliando um serviço de backend gerenciado.


Próximo Passo: Rode o Teste de Carga Antes do Seu Próximo Patch

Escolha a data do seu próximo patch. Duas semanas antes, rode o script k6 acima contra o endpoint do seu CDN. Se sua latência p95 exceder 200ms na escala simulada de lançamento, você tem tempo para corrigir. Se descobrir que sua taxa de cache hit cai abaixo de 90% durante a fase de sustentação, ative origin shielding e estenda os TTLs dos seus assets.

A diferença entre um lançamento suave e um desastre no dia do lançamento raramente está no código do jogo. Está na infraestrutura que serve os 2 GB de assets que cada jogador baixa nos primeiros cinco minutos. Acertou isso, e o resto é gameplay.