Por Que a Memória do Cache DNS Importa

Na escala da Cloudflare, o cache DNS armazena mais de 250 bilhões de entradas a qualquer momento. Um único byte desperdiçado por entrada equivale a 250 GB de memória em toda a frota. Isso não é só sobre custo—é sobre desempenho. Um cache mais eficiente em memória significa taxas de acerto maiores, menor latência e melhor experiência para o usuário.

Identificamos cinco otimizações sucessivas que reduziram o uso de memória por entrada de 953 bytes para 420 bytes—uma redução de 56%. O resultado: cerca de 100 TB de RAM liberados, equivalentes a 130 servidores Gen 13. Mas não sacrificamos velocidade. A taxa de inserção subiu 43% e a latência de lookup caiu 19%.

A Primeira Vitória: Substituir Vec por Box<[T]>

O tipo Vec em Rust inclui um campo de capacidade—8 bytes por vetor. Mas depois que armazenamos uma resposta DNS, nunca a modificamos. Trocar para Box<[T]> elimina o campo de capacidade e a super-alocação. Aplicamos isso a todos os 8 campos de vetor em cada entrada de cache, economizando 64 bytes por entrada. Isso sozinho liberou mais de 15 TB na frota.

// Antes: Vec com overhead de capacidade
pub struct CacheEntry {
    answers: Vec<Record>,
    authority: Vec<Record>,
    additional: Vec<Record>,
    // ...
}

// Depois: Box<[T]> sem capacidade extra
pub struct CacheEntry {
    answers: Box<[Record]>,
    authority: Box<[Record]>,
    additional: Box<[Record]>,
    // ...
}

Server rack with DNS cache memory optimization dashboard overlay Programming Illustration

Consolidando Listas e Removendo Donos

Em vez de armazenar as seções de resposta, autoridade e adicional como listas separadas, armazenamos uma única lista com offsets de 2 bytes para cada seção. Isso remove dois ponteiros e dois comprimentos por entrada—economizando 28 bytes.

// Antes: Três boxes separados
pub struct CacheEntry {
    answers: Box<[Record]>,
    authority: Box<[Record]>,
    additional: Box<[Record]>,
}

// Depois: Buffer único com offsets
pub struct CacheEntry {
    records: Box<[u8]>,
    answer_offset: u16,
    authority_offset: u16,
    // ...
}

Também notamos que a maioria dos registros DNS tem um dono idêntico ao domínio consultado. Ao armazenar Option<Name> para o campo dono, podemos inferir o domínio a partir da chave do cache quando é None, evitando uma alocação no heap para a maioria dos registros.

Data analyst examining memory usage graphs and performance metrics for DNS cache Dev Environment Setup

Tamanho de Enum e Armazenamento em Formato de Rede

Enums em Rust são dimensionados pelo seu maior variante. Nosso enum RecordData tinha um variante NAPTR com 136 bytes, então cada registro A (4 bytes) desperdiçava 120 bytes. Colocar variantes grandes em Box moveu-os para o heap, mas introduziu overhead de alocador e má localidade de memória.

O avanço veio ao armazenar dados de registro em formato de rede como bytes brutos com um prefixo de comprimento de 2 bytes. Isso eliminou o overhead por variante e melhorou a localidade do cache.

// Antes: Enum com variantes grandes
pub enum RecordData {
    A(Ipv4Addr),
    Aaaa(Ipv6Addr),
    Naptr(Naptr),
    // ...
}

// Depois: Variantes grandes em Box
pub enum RecordData {
    A(Ipv4Addr),
    Aaaa(Ipv6Addr),
    Txt(Box<Txt>),
    Naptr(Box<Naptr>),
    // ...
}

Benchmarks e Resultados em Produção

Nossos benchmarks mostraram que o footprint por entrada caiu de 953 para 420 bytes, e as alocações de 1.1 KB para 461 bytes. Em produção, o uso de memória p99 caiu de 9.3 GB para 5.3 GB por instância. A economia agregada chegou a 100 TB.

MétricaAntesDepoisMudança
Footprint líquido por entrada953 bytes420 bytes-56%
Alocações por entrada1.1 KB461 bytes-58%
Taxa de inserção no cache625.000 entradas/s893.000 entradas/s+43%
Latência de lookup no cache828 ns670 ns-19%

Limitações e Considerações

Essas otimizações não são gratuitas. O armazenamento em formato de rede requer iteração sequencial, complicando recursos como rotação round-robin. Colocar variantes grandes em Box pode prejudicar a localidade de memória se não for cuidadosamente gerenciado. Além disso, os resultados dependem da mistura de tráfego—locais com muitos ECS se beneficiam mais.

Próximos Passos para o Seu Código

Se você trabalha com sistemas de alta performance, considere estas dicas:

  • Perfile suas estruturas de dados: Identifique campos que carregam overhead desnecessário.
  • Use Box<[T]> em vez de Vec para dados imutáveis.
  • Armazene dados em formatos compactos (ex.: formato de rede) quando o acesso aleatório não for crítico.
  • Meça tanto memória quanto desempenho para evitar trade-offs.

Para mais insights sobre engenharia resiliente, confira nosso Cloudflare Code Orange fail small engineering insights. E se você está explorando plataformas de IA de borda, veja nossa análise da NVIDIA IGX Thor.

Fonte: Blog da Cloudflare

Network diagram showing DNS query flow with optimized cache storage Development Concept Image

Este conteúdo foi elaborado com o auxílio de ferramentas de IA, com base em fontes confiáveis, e revisado pela nossa equipe editorial antes da publicação. Não substitui o aconselhamento de um profissional especializado.