O cache DNS da Cloudflare guarda mais de 250 bilhões de entradas. Nessa escala, economizar um byte por entrada representa mais de 250 GB espalhados pela frota. A empresa levou essa aritmética a sério: cinco mudanças no layout de dados do Big Pineapple, plataforma por trás do 1.1.1.1 e de outros serviços DNS, liberaram aproximadamente 100 TB de memória.
O resultado chama atenção, mas a parte útil para outros sistemas está no caminho. Não houve um algoritmo secreto. Houve uma sequência de decisões sobre capacidade que nunca seria usada, ponteiros repetidos, dados duplicados, tamanho de enums e localidade de memória.
1. Trocar coleções que crescem por dados de tamanho fixo
Uma entrada entra no cache pronta e não é modificada depois. Mesmo assim, vários campos usavam Vec<T> e String, estruturas que guardam ponteiro, tamanho e capacidade. A capacidade existe para permitir crescimento futuro — algo que nunca acontecia naquele ciclo de vida.
A troca por Box<[T]> e Box<str> removeu o campo de capacidade e o espaço reservado além do conteúdo real. Como cada entrada tinha oito campos desse tipo, foram 64 bytes a menos na estrutura, além da sobra eliminada no heap. Sozinha, a mudança economizou mais de 15 TB.
2. Juntar listas e guardar apenas os limites
Uma resposta DNS tem seções de resposta, autoridade e dados adicionais. Guardá-las em três listas separadas repete ponteiros e tamanhos. A Cloudflare reuniu os registros numa lista única e armazenou offsets de 16 bits indicando onde cada seção começa.
A economia calculada foi de 28 bytes por entrada. O time também agrupou vários booleanos em bitflags. Aqui aparece um detalhe comum em Rust, C e C++: o tamanho removido dos campos não conta toda a história, porque alinhamento e padding podem encolher junto com eles.
3. Não repetir o nome que já estava na chave
Cada registro DNS tem um owner, o domínio ao qual pertence. Na maior parte das respostas, esse nome é igual ao domínio consultado e já existe na chave do cache. A implementação antiga ainda guardava uma cópia completa junto de cada registro.
O novo modelo usa um owner opcional. Quando ele é igual à consulta, None significa “recupere da chave”. Quando um CNAME faz o owner mudar, o nome completo continua armazenado. É uma forma controlada de normalização: perde-se a independência do registro, mas elimina-se uma alocação no caso comum.
4. Impedir que o caso raro determine o tamanho de todos
Os dados de cada tipo de registro eram representados por um enum. Em Rust, o enum precisa comportar sua maior variante. Nesse caso, um NAPTR fazia a estrutura chegar a 144 bytes, embora registros A precisem de 4 bytes e AAAA, de 16. A e AAAA respondem por mais de 80% do tráfego medido.
Colocar variantes grandes em Box reduziu o tamanho inline e fez o caso comum deixar de pagar pelo raro. A troca, porém, trouxe alocações extras, arredondamento do allocator e ponteiros para regiões distantes do heap. Foi um ganho intermediário, não o fim da história.
5. Guardar os registros próximos do formato de saída
A etapa seguinte substituiu a lista de enums por um único Box<[u8]>. Cada registro fica codificado como comprimento de dois bytes seguido dos bytes em formato DNS. Isso remove o custo do enum e as alocações individuais introduzidas pelo boxing, além de manter os dados contíguos.
Há uma perda: os registros precisam ser percorridos em sequência. Como cada entrada contém poucos registros, o custo foi pequeno. Em troca, muitos tipos podem ser copiados direto para a resposta, sem serialização campo a campo. Registros com nomes, como CNAME, MX e SOA, ainda precisam ser interpretados para aplicar compressão DNS.
Na inserção, um buffer reutilizável funciona como área de trabalho e só no final é copiado para a alocação de tamanho exato. A Cloudflare mediu aumento de 13% na taxa de inserção apenas nessa mudança.
Menos memória também deixou o cache mais rápido
Nos benchmarks, a memória líquida por entrada caiu de 953 para 420 bytes, redução de 56%. As alocações passaram de 1,1 KB para 461 bytes. Inserções subiram de 625 mil para 893 mil entradas por segundo, ganho de 43%, e a latência de busca caiu de 828 para 670 nanossegundos, 19% menor.
Produção mostrou uma redução menor porque a memória residente inclui muito mais do que o cache. Ainda assim, o p99 por instância caiu de 9,3 para 5,3 GB. A implantação ocorreu em etapas entre maio e julho, e a empresa mediu os patamares estáveis após o preenchimento dos caches, não apenas a queda artificial de processos recém-reiniciados.
A lição não é trocar todo Vec por Box
Cada otimização dependia do perfil real: entradas imutáveis, poucos registros por resposta, predominância de A e AAAA e centenas de bilhões de itens. Em outra aplicação, a lista pode crescer, o acesso aleatório pode ser essencial ou a variante grande pode ser frequente.
O método é mais reaproveitável que o patch: medir alocações, modelar uma carga parecida com produção, atacar o caso comum, checar CPU junto com memória e validar no processo real. Em escala suficiente, layout de dados deixa de ser um detalhe interno e vira capacidade de infraestrutura.
