Cloudflare cortou 100 TB de RAM no cache DNS do 1.1.1.1 com cinco otimizações em Rust
Ao repensar como cada entrada é armazenada na memória, a Cloudflare reduziu o consumo por entrada em 56% e ainda ganhou velocidade. A engenharia por trás é replicável em qualquer sistema que lide com volume.

A Cloudflare publicou em 27 de agosto de 2026 um detalhamento de engenharia sobre como reduziu em mais de 50% a memória usada por cada entrada no cache DNS do Big Pineapple, a plataforma em Rust↳Rust7 conteúdosDesmistificando Rust: a linguagem segura e rápida que você precisa conhecerDev (Back & Front) · out 2024Como criar seu primeiro Programa em Rust com Solana PlaygroundDev (Back & Front) · mai 2026Rust no ranking: a segurança de memória perdeu a guerra corporativa?Marketing Tech · abr 2026Ver tudo em Dev (Back & Front) → que sustenta o resolver 1.1.1.1, o Gateway DNS, o DNS Firewall e o AS112. A escala do problema é o que torna o caso instrutivo: o Big Pineapple mantém mais de 250 bilhões de entradas de cache a qualquer momento. Nesse regime, desperdiçar um único byte por entrada custa mais de 250 GB de RAM em toda a frota.
O resultado agregado: cinco mudanças sucessivas liberaram cerca de 100 terabytes de memória, o equivalente à RAM de 130 servidores da geração 13 da empresa. E não houve troca de espaço por velocidade: o throughput de inserção subiu 43% e a latência de lookup caiu 19%.
O que estava sendo desperdiçado
Cada item do cache é um par chave-valor. A chave identifica a consulta (qname, qtype, flag de autenticação, tag); o valor guarda a resposta DNS (seções answer, authority e additional), mais metadados como timestamp, contador de hits e TTL. O ponto central do artigo é que várias dessas estruturas usavam tipos com overhead desnecessário depois que a entrada já está armazenada e nunca mais é modificada.
As cinco otimizações atacam exatamente esse desperdício:
- Trocar
VecporBox<[T]>eStringporBox. UmVeccarrega ponteiro, comprimento e capacidade. Como a resposta nunca cresce depois de cachada, o campo de capacidade (8 bytes) e o espaço heap reservado para crescimento futuro são puro desperdício. A troca economiza 8 bytes por campo, 64 bytes por entrada, e mais de 15 TB somando as 250 bilhões de entradas. - Uma lista só, com offsets. Em vez de três listas separadas (answer, authority, additional), uma única lista com offsets
u16(2 bytes) marcando o início de cada seção, no lugar do par ponteiro+comprimento de 8+8 bytes de cadaBox<[T]>. São 28 bytes por entrada, e o empacotamento de booleanos num bitflag ainda eliminou padding extra. - Descartar o owner quando ele é igual ao domínio consultado. Na maioria dos registros o owner coincide com o
qname, então o campo viraOption>e, quando éNone, o nome é reconstruído a partir da chave do cache na hora da leitura, evitando uma alocação no heap. Quando há CNAME e o owner difere, oSomeguarda o ponteiro. - Boxing das variantes grandes do enum. Um enum Rust tem sempre o tamanho da maior variante. O
RecordDataera dimensionado pelo NAPTR (136 bytes, virando 144 com tag e padding), enquanto registros A (4 bytes) e AAAA (16 bytes) representam mais de 80% do tráfego. Colocar só as variantes grandes numBoxeconomiza 120 bytes por registro A/AAAA. - Guardar os registros em wire format. Em vez de uma lista de variantes de enum, um único
Box<[u8]>com cada registro codificado como prefixo de comprimento de 2 bytes seguido dos bytes crus. Isso elimina o overhead por variante, empacota os dados de forma contígua (melhor localidade de cache da CPU) e permite copiar A, AAAA, TXT e registros DNSSEC direto para a resposta, sem re-serializar campo a campo.
Os números lado a lado
O benchmark da Cloudflare enche o cache com entradas geradas aleatoriamente na distribuição de produção (56% A, 25% AAAA, 19% TXT), medindo memória com um alocador customizado que envolve o System allocator do Rust.
| Métrica | Antes | Depois | Variação |
|---|---|---|---|
| Footprint líquido por entrada | 953 bytes | 420 bytes | -56% |
| Alocações por entrada | 1,1 KB | 461 bytes | -58% |
| Throughput de inserção | 625.000 entradas/s | 893.000 entradas/s | +43% |
| Latência de lookup | 828 ns | 670 ns | -19% |
Em produção, a economia é menor porque a memória residente inclui muito além do cache: no p99, o uso caiu de 9,3 GB para 5,3 GB por instância (43%); no p90, de 6,5 GB para 3,8 GB (42%). O rollout foi gradual, de 18 de maio a 6 de julho de 2026, e cada release trouxe uma ou mais das otimizações, o que fez a memória cair em degraus.
O trade-off que a boxing introduz (e como foi resolvido)
O artigo é honesto sobre custos. Fazer boxing das variantes cria uma alocação separada por registro, sujeita ao arredondamento por bin do jemalloc (um registro MX pede 40 bytes e é arredondado para 48, desperdiçando 8), e piora a localidade de memória, já que cada registro passa a viver espalhado no heap. A solução de wire format resolve os dois: uma única alocação por entrada, dados contíguos. Escrevendo primeiro num buffer de scratchspace reutilizável e depois copiando para um Box<[u8]> dimensionado, o insert throughput ganhou 13% só nessa mudança.
O que a comunidade destacou
O caso rendeu discussão sobre até onde vai a segurança do Rust. No thread do Hacker News, o usuário vinkelhake levanta um ponto sobre juntar as três listas em uma só:
"If you previous had three distinct Vec objects, then Rust would guarantee that you can't index out of bounds. If you now put all those objects into a single Vec and rely on offsets, then you now open the door to indexing out of range of these sub-slices without any panics."
vinkelhake
Já irdc resume o espírito do artigo: "This is why system programming still matters", e sugere que faltaria a otimização óbvia de colocar os dados do registro logo após os membros do CacheEntry em vez de alocar separadamente, ressalvando que isso pode ser "not be all that easy in Rust". O usuário strenholme trouxe um caso concreto do próprio projeto MaraDNS, onde trocar um malloc() por entrada por um único malloc() grande derrubou uma blacklist de 237 megabytes para 9,5 megabytes.
O que isso muda para quem constrói software no Brasil
Nenhuma dessas técnicas depende da escala da Cloudflare para valer a pena. O padrão é o mesmo em qualquer cache, fila ou estrutura de dados imutável depois de criada: quando um objeto nunca mais cresce, o campo de capacidade e o espaço reservado do Vec são desperdício que se multiplica por milhões. A régua prática que o artigo oferece para times menores:
- Prefira tipos imutáveis de tamanho fixo (
Box<[T]>,Box) para dados que não mudam depois de armazenados, em vez de containers que reservam espaço para crescimento. - Cuidado com o custo do enum dimensionado pela maior variante: se 80% do tráfego é da variante pequena, o padding da variante rara está sendo pago em toda entrada.
- Localidade de memória importa tanto quanto tamanho: empacotar dados contíguos reduziu latência de lookup em 5% nos benchmarks, mesmo sem cortar bytes.
- Meça com um alocador instrumentado. A Cloudflare só enxergou o desperdício porque contabilizou número e tamanho de alocações por entrada, e validou em produção o que o benchmark previa.
A Cloudflare planeja reinvestir a memória liberada em aumentar a capacidade do cache sem subir o consumo de RAM, o que melhora o hit rate e reduz o volume de consultas upstream. Para quem opera resolvers, CDNs ou qualquer sistema com cache em RAM no Brasil, o retorno de um exercício de dimensionamento de struct pode ser desproporcional ao esforço.
Fontes: Hacker News · Reações no Hacker News
Este artigo foi escrito por Redação iMasters, um agente de inteligência artificial com revisão editorial humana. Publicado sob revisão editorial de Rafael Chinaglia - iMasters e validação técnica de Tiago Baeta. Saiba como produzimos no expediente.









Comentários
Ninguém comentou ainda. Começa a conversa?