Comment Cloudflare a économisé 100 To de RAM en optimisant le cache DNS de 1.1.1.1 en Rust
En modifiant la structure en mémoire des entrées de son cache DNS (Domain Name System, le système qui traduit les noms de domaine en adresses IP), Cloudflare a réduit de 56 % l'empreinte mémoire par élément. Cette réarchitecturation logicielle a permis de libérer près de 100 téraoctets de RAM (mémoire vive) sur l'ensemble de ses serveurs tout en améliorant les performances.
Fil « Optimisation mémoire du cache DNS 1.1.1.1 par Cloudflare »

Le moteur Big Pineapple, qui alimente notamment le service 1.1.1.1 et les pare-feux DNS de Cloudflare, gère en permanence plus de 250 milliards d'entrées en cache. À cette échelle, chaque octet gaspillé par entrée représente un surcoût de 250 gigaoctets à l'échelle de l'infrastucture globale. L'équipe d'ingénierie a conduit cinq optimisations successives sur la structure de données des entrées stockées en Rust pour réduire drastiquement ce volume.
Chasser le surcoût des structures dynamiques
Initialement, les entrées du cache et les clés associées utilisaient les types Vec<T> (vecteurs dynamiques) et String pour stocker les enregistrements et les métadonnées. En Rust, un Vec conserve en mémoire trois éléments de 8 octets : un pointeur vers le tas (heap, l'espace de mémoire dynamique), la longueur actuelle et la capacité totale réservée. Or, une fois qu'une réponse DNS est mise en cache, elle n'est plus jamais modifiée. La capacité réservée devient inutile et gaspille de l'espace.
En remplaçant les types dynamiques Vec<T> et String par des tranches figées allouées sur le tas Box<[T]> et Box<str>, Cloudflare a supprimé le champ de capacité (gagnant 8 octets par champ) et éliminé la mémoire sur-allouée en réserve. Avec 8 champs concernés par entrée, cette seule étape a permis de libérer 64 octets par entrée, soit plus de 15 téraoctets de RAM au total.
// Avant : structures avec Vec dynamiques
pub struct CacheEntry {
pub answers: Vec<Record>,
pub authority: Vec<Record>,
pub additional: Vec<Record>,
}
// Après : regroupement et stockage binaire brut compacté
pub struct CacheEntry {
// Les sections d'enregistrements sont fusionnées dans un tampon binaire continu
pub records: Box<[u8]>,
pub authority_offset: u16,
pub additional_offset: u16,
}
Fusion des sections et élimination du rembourrage
Au lieu de conserver trois listes distinctes pour les sections de réponses (réponses principales, autorité et informations additionnelles), les ingénieurs ont regroupé les données dans une liste unique. Ils ont remplacé les pointeurs et longueurs de listes par deux simples décalages (offsets) codés sur 2 octets (u16). Cette modification a économisé 28 octets par entrée.
L'équipe a également tiré parti de la gestion du padding (octets de bourrage ajoutés par le compilateur Rust pour aligner les données en mémoire selon les exigences matérielles). En compactant plusieurs variables booléennes dans une structure de bits (bitflag), le compilateur a supprimé les espaces vides d'alignement. De plus, pour les enregistrements dont le nom de domaine propriétaire est identique à la requête d'origine (le cas le plus fréquent), le champ du nom a été supprimé puis déduit à la lecture pour éviter des allocations d'espace superflues.
Du problème des enums Rust au format binaire brut
En Rust, les types énumérés (enums ou types sommes) occupent en mémoire la taille de leur plus grande variante. La structure RecordData mesurait 144 octets à cause du type d'enregistrement NAPTR, alors que les enregistrements A (adresses IPv4, codées sur 4 octets) et AAAA (adresses IPv6, codées sur 16 octets) représentent plus de 80 % du trafic. Les enregistrements courants gâchaient ainsi plus de 120 octets de rembourrage chacun.
Après avoir tenté d'isoler les variantes volumineuses sur le tas via des pointeurs (Box), l'équipe a fait le choix de stocker la donnée des enregistrements directement sous forme de données binaires brutes (Box<[u8]>) préfixées par leur longueur. Ce format compact supprime le surcoût de l'énumération et garantit une meilleure contiguïté en mémoire, améliorant la mise en cache au niveau du processeur (CPU cache line, le bloc de mémoire transféré directement dans le cache rapide du processeur).
Des gains massifs de mémoire et de rapidité
Ces cinq ajustements successifs ont permis d'atteindre des résultats majeurs en production :
- 56 % de réduction de l'empreinte mémoire moyenne par entrée en cache ;
- 100 téraoctets de RAM libérés sur l'ensemble du réseau (l'équivalent de la mémoire de 130 serveurs de 13e génération) ;
- +43 % de débit lors des insertions dans le cache ;
- -19 % de latence sur les lectures, grâce à la réduction des allocations dynamiques et à une meilleure localité mémoire.