100 Terabytes Recuperados

A plataforma Big Pineapple, responsável pelo serviço de DNS 1.1.1.1 da Cloudflare, Gateway DNS, DNS Firewall e AS112, implementou cinco otimizações sucessivas na forma como armazena entradas de cache em memória. O resultado foi uma redução de mais de 50% no espaço ocupado por cada entrada. Na escala da empresa, que mantém mais de 250 bilhões de entradas de cache DNS a qualquer momento, a economia totalizou aproximadamente 100 terabytes de memória, equivalente à quantidade de RAM presente em 130 servidores Gen 13.

Desempenho Também Melhorou

As mudanças não comprometeram a velocidade do sistema. A taxa de inserção de dados no cache subiu 43%, enquanto a latência nas consultas caiu 19%. Segundo a equipe, menos alocações de memória e melhor locality dos dados explicam o ganho simultâneo em espaço e velocidade.

Estrutura do Cache

Cada entrada no cache DNS funciona como um par de chave-valor. A chave identifica o que foi consultado, enquanto o valor armazena a resposta DNS completa: seções de resposta, autoridade e adicional, além de metadados como tempo de criação, contador de acessos e TTL (Time-to-Live). Quando o EDNS Client Subnet está em uso, servidores autoritativos retornam respostas diferentes conforme a rede do cliente, multiplicando o número de versões armazenadas para a mesma consulta.

Primeira Otimização: Box vs Vec

A primeira grande mudança envolveu substituir Vec por Box<[T]> e String por Box. O problema com Vec é que ele armazena três campos: ponteiro para dados no heap, comprimento atual e capacidade total. Quando uma resposta DNS é armazenada no cache, ela nunca mais é modificada. O campo de capacidade se torna desnecessário, mas ainda consome 8 bytes. Além disso, o Vec往往会预留额外的堆内存空间,导致资源浪费。Box<[T]>无法在创建后增长,因此不需要capacity字段。替换8个Vec和String字段后, economizam-se 64 bytes por entrada, o que totaliza mais de 15 terabytes no fleet.

Segunda Otimização: Offsets em vez de Listas

Em vez de armazenar as seções de resposta, autoridade e adicional em listas separadas, a equipe passou a usar uma única lista com offsets indicando onde cada seção começa. Como a contagem de registros DNS por seção cabe em um u16, cada offset precisa de apenas 2 bytes. Antes, cada lista separada exigia um ponteiro de 8 bytes e um comprimento de 8 bytes. A mudança remove duas listas inteiras e as substitui por dois offsets de 2 bytes, economizando 28 bytes por entrada.

Terceira Otimização: Compressão de Nomes DNS

Cada registro DNS possui um owner, o domínio ao qual pertence. Na maioria dos casos, esse owner é idêntico ao domínio consultado. Quando um CNAME está envolvido, porém, o owner pode ser diferente. O formato wire DNS lida com owners repetidos usando compressão de nomes, conforme definido na RFC 1035, mas a equipe optou por armazenar o nome completo em cada entrada para evitar seguir ponteiros durante consultas. Para registros cujo owner é igual ao domínio consultado, o sistema simplesmente omite o campo owner, inferindo-o na leitura a partir da chave do cache.

Quarta Otimização: Bitflags

A equipe também compactou diversos campos booleanos em um único bitflag. Como Rust insere padding para satisfazer requisitos de alinhamento, remover um campo pequeno pode eliminar padding adicional. A redução no tamanho da struct foi maior que a soma individual dos booleanos removidos.

Metodologia de Testes

Para medir o impacto de cada mudança, a equipe usou entradas geradas aleatoriamente que reproduzem a distribuição de tráfego em produção: 56% registros A, 25% AAAA e 19% TXT. Cada entrada contém entre um e quatro registros. Registros TXT servem como替代品 para todos os tipos não-A/AAAA, com tamanhos variando entre 64 e 224 bytes, aproximando a resposta média de tipos de registro de comprimento variável.

Com informações de: Hacker News