Ce qui se passe
Big Pineapple, la plateforme Rust derrière 1.1.1.1, Gateway DNS, DNS Firewall et AS112, garde en permanence plus de 250 milliards d'entrées de cache DNS. À cette échelle, un octet gaspillé par entrée coûte 250 Go sur la flotte. Cinq changements successifs de représentation en mémoire ont divisé l'empreinte par plus de deux, sans modifier un seul algorithme.
Les cinq changements
1. Supprimer la capacité. Un Vec<T> porte trois champs : pointeur, longueur
et capacité. Une réponse DNS mise en cache n'est plus jamais modifiée, donc la
capacité ne sert à rien — 8 octets par champ, plus l'espace sur le tas réservé
« au cas où ». Box<[T]> et Box<str> s'en passent. Huit champs par entrée : 64
octets, soit plus de 15 To à l'échelle de la flotte.
2. Une liste au lieu de trois. Les sections answer, authority et
additional deviennent une liste unique avec deux décalages u16, puisqu'un
compte d'enregistrements tient sur 16 bits. Deux pointeurs et deux longueurs de
8 octets remplacés par 2 + 2 octets : 28 octets par entrée. Les booléens
regroupés en bitflags font gagner davantage que leur taille, en supprimant du
remplissage d'alignement.
3. Ne plus stocker le propriétaire quand il est déductible. Le owner d'un
enregistrement est presque toujours le domaine interrogé — sauf derrière un
CNAME. Il devient optionnel, et la clé de cache, déjà disponible à chaque
lecture, le restitue :
pub struct Record {
owner: Option<Box<Name>>,
class: Class,
ttl: Ttl,
rtype: Rtype,
data: RecordData,
}4. Boxer les grandes variantes. Une énumération Rust fait toujours la taille
de sa plus grande variante : ici NAPTR, 136 octets, soit 144 avec le tag et le
remplissage. Or un A tient sur 4 octets, un AAAA sur 16, et ces deux types
représentent plus de 80 % du trafic — 120 octets perdus par enregistrement.
Déplacer Txt, Naptr et Svcb sur le tas règle le cas, au prix de deux
défauts : jemalloc arrondit à la classe de taille supérieure (un MX demande 40
octets et en occupe 48) et les données se dispersent, ce qui coûte des lignes de
cache.
5. Garder les données au format wire. Plutôt qu'une liste de variantes
analysées, un seul Box<[u8]> où chaque enregistrement est préfixé de sa
longueur sur 2 octets. Les deux défauts précédents disparaissent, la lecture
devient une copie directe pour A, AAAA, TXT et DNSSEC — seuls CNAME,
NS, MX et SOA demandent encore une analyse, pour la compression de noms.
Gain : 5 % de latence. L'écriture passe par un tampon de travail réutilisé puis
un memcpy unique : 13 % d'insertions en plus à elle seule.
Stocker le message entier au format wire a été écarté : le drapeau DNSSEC OK obligerait à cacher deux variantes de chaque réponse.
Les résultats
| Mesure | Avant | Après | Écart |
|---|---|---|---|
| Empreinte par entrée | 953 octets | 420 octets | −56 % |
| Allocations par entrée | 1,1 Ko | 461 octets | −58 % |
| Débit d'insertion | 625 000 /s | 893 000 /s | +43 % |
| Latence de lecture | 828 ns | 670 ns | −19 % |
En production, sur les instances Big Pineapple, la mémoire résidente p99 est passée de 9,3 à 5,3 Go entre le 18 mai et le 6 juillet 2026, date de fin du déploiement. Soit environ 100 To libérés — l'équivalent de la RAM de 130 serveurs Gen 13 — que Cloudflare compte réinvestir en capacité de cache plutôt qu'en économies.
Source : The Cloudflare Blog, Saving 100 terabytes of memory by optimizing 1.1.1.1's DNS cache