Jak Cloudflare ušetřil 100 terabajtů paměti optimalizací DNS cache pro 1.1.1.1

Cloudflare v cache svého DNS resolveru 1.1.1.1 drží přes 250 miliard položek, takže i úspora jednoho bajtu na položku se počítá. Pěti úpravami toho, jak jsou záznamy uložené v paměti, se týmu podařilo zmenšit jednu položku o 56 % a napříč celou flotilou uvolnit zhruba 100 terabajtů RAM, aniž by to stálo výkon.
Cloudflare provozuje pod svými DNS službami (1.1.1.1, Gateway DNS, DNS Firewall, AS112) interní platformu Big Pineapple, která v jednu chvíli drží přes 250 miliard položek DNS cache. Při takovém objemu stačí promarnit jediný bajt na položku a stojí to přes 250 GB paměti napříč celou flotilou serverů.
Tým prošel pět úprav toho, jak jsou položky cache uložené v paměti (celý systém je napsaný v Rustu), a zmenšil jejich velikost o víc než polovinu. Napříč infrastrukturou to uvolnilo zhruba 100 terabajtů, tedy tolik RAM, kolik má dohromady 130 jejich serverů generace 13. A cache se u toho nezpomalila, spíš naopak: vkládání zrychlilo o 43 % a vyhledávání o 19 %, protože méně alokací a lepší lokalita v paměti znamenaly rychlejší práci, ne kompromis mezi rychlostí a místem.
Co se cachuje
Položka cache je dvojice klíč-hodnota. Klíč nese doménu, typ dotazu a pár dalších identifikátorů. Hodnota drží samotnou DNS odpověď (sekce answer, authority, additional) a metadata jako TTL nebo počítadlo zásahů. Obě struktury používaly datové typy s režií navíc, kterou už jednou uložený a dál neměnný záznam vlastně nepotřebuje – a přesně na tom staví všech pět úprav.
1. Konec s Vec, nastupuje Box<[T]>
Vec<T> v Rustu drží tři věci: ukazatel na data, aktuální délku a kapacitu, protože počítá s tím, že se do něj bude ještě přidávat. Jenže záznam v cache se po uložení už nemění. Pole s kapacitou (8 bajtů) je tak k ničemu a navíc Vec typicky rezervuje na haldě víc místa, než kolik prvků skutečně obsahuje.
pub struct CacheEntry {
pub answers: Vec<Record>, // pointer + délka + kapacita
pub authority: Vec<Record>,
pub additional: Vec<Record>,
// ...
}
Code language: HTML, XML (xml)
Řešením je Box<[T]> (a Box<str> místo String), který po vytvoření růst nemůže, takže žádnou rezervu ani pole s kapacitou nepotřebuje. Na 8 polích tohoto typu v jedné položce to dělá 64 ušetřených bajtů na položku, při 250 miliardách položek přes 15 terabajtů.
2. Méně seznamů, méně ukazatelů
Místo tří samostatných seznamů (answer, authority, additional) stačí jeden společný seznam a k němu dvoubajtové offsety, kde která sekce začíná – počty záznamů v jedné sekci se totiž vejdou do u16. To nahradí dva 16bajtové seznamy (8bajtový ukazatel + 8bajtová délka) dvěma 2bajtovými čísly, úspora 28 bajtů na položku. Odstranění malého pole navíc často smaže i kus zarovnávacího paddingu okolo (Rust zarovnává velikost struktur na násobky), takže se výsledek zmenší o víc, než by odpovídalo jen prostému součtu odebraných bajtů.
3. Zahození vlastníka záznamu
Vlastník DNS záznamu, tedy doména, ke které patří, je skoro vždy shodný s doménou, na kterou se dotaz ptal. Liší se jen tam, kde je v odpovědi CNAME a za ním záznamy jiné domény. Big Pineapple proto vlastníka ve většině případů vůbec neukládá a dopočítá ho při sestavování odpovědi z klíče cache, který je při každém vyhledání stejně po ruce. Plné jméno se uloží jen tam, kde se skutečně liší.
4. Boxování velkých variant enumu
Rustí enumy jsou tzv. sum types: každá varianta může nést jiná data, ale celý enum je vždy velký jako jeho největší varianta. U typů DNS záznamů (A, AAAA, TXT, NAPTR…) je tou největší NAPTR s 144 bajty, přestože A a AAAA, dohromady přes 80 % provozu, potřebují jen 4, respektive 16 bajtů. Většina záznamů tak zbytečně plýtvala přes 120 bajty na výplň.
pub enum RecordData {
A(Ipv4Addr), // zůstává přímo v enumu
Aaaa(Ipv6Addr),
Txt(Box<Txt>), // jde na haldu
Naptr(Box<Naptr>),
// ...
}
Code language: JavaScript (javascript)
Řešením je přesunout velké varianty na haldu přes Box. Enum pak drží jen 8bajtový ukazatel a data na haldě zabírají přesně tolik, kolik potřebují. Cenou je horší lokalita v paměti (boxovaná data se rozptýlí po haldě a čtení znamená sledovat ukazatel) a drobná režie alokátoru při zaokrouhlování na velikostní třídy, ale u malých a častých typů to výrazně převáží.
5. Záznamy ve formátu na drátě
Poslední krok jde ještě dál: místo rozparsovaných enum variant se data záznamů ukládají jako syrové bajty v jednom Box<[u8]>, kde je každý záznam uvozený dvoubajtovou délkou. Odpadá tak režie samotného enumu i boxované alokace z předchozího kroku a data navíc leží souvisle vedle sebe, což pomáhá procesorové cache. Cenou je, že se v záznamech nedá libovolně indexovat, musí se procházet postupně – u malého počtu záznamů v položce je to ale zanedbatelné.
Při sestavování odpovědi se navíc většina typů (A, AAAA, TXT, DNSSEC) dá zkopírovat rovnou z bufferu bez parsování; jen záznamy se jmény domén (CNAME, NS, MX, SOA) se pořád musí parsovat kvůli kompresi jmen. Tahle změna sama o sobě zrychlila vyhledávání o 5 % a vkládání do cache o 13 %.
Výsledky
Nasazování probíhalo od 18. května do 6. července 2026. V produkci klesla spotřeba paměti na instanci na p99 z 9,3 GB na 5,3 GB (−43 %), na p90 z 6,5 GB na 3,8 GB (−42 %).
| Metrika | Před | Po | Změna |
|---|---|---|---|
| Paměť na položku | 953 B | 420 B | −56 % |
| Alokace na položku | 1,1 KB | 461 B | −58 % |
| Propustnost vkládání | 625 000/s | 893 000/s | +43 % |
| Latence vyhledávání | 828 ns | 670 ns | −19 % |
Uvolněnou paměť chce Cloudflare využít na zvětšení kapacity cache, aniž by rostla celková spotřeba, což by mělo zlepšit poměr zásahů a snížit počet dotazů posílaných dál na autoritativní servery.
Zdroj: Cloudflare Blog – How we saved 100 terabytes of memory by optimizing 1.1.1.1’s DNS cache
… liked this!
… reposted this!
… liked this!
… liked this!