Cloudflare’s Big Pineapple Rewrite Turns Every DNS Cache Entry Into a Memory Tax
Cloudflare cut its DNS cache's per-entry memory footprint by 56% through five successive Rust struct changes, freeing about 100 terabytes of memory across its fleet.
Cloudflare’s DNS platform, internally named Big Pineapple, answers queries for 1.1.1.1, Gateway DNS, DNS Firewall, AS112, and several other Cloudflare services. At any given moment it holds more than 250 billion cache entries in memory. At that scale, a single wasted byte per entry adds up to more than 250 gigabytes across the fleet, so the engineering team went looking for waste, one struct field at a time.
Table Of Content
- What a Cache Entry Actually Holds
- Benchmarking Real Traffic, Not a Synthetic Load
- Five Changes, Each Attacking a Different Kind of Waste
- The Capacity Field Nobody Needed
- Three Lists Became One
- Inferring the Owner Instead of Storing It
- An Enum Sized for Its Biggest Member
- Boxing the Big Variants, and What That Costs
- Skipping the Struct Entirely
- What Five Small Changes Added Up To
- Why a Struct Layout Rewrite Is Worth a Blog Post
In an August 27, 2026 post on the Cloudflare Blog, engineer Sebastiaan Neuteboom walked through five successive changes to how Big Pineapple stores cache entries in Rust. None of them touch the DNS protocol or change what gets served back to a resolver. Each one just asks whether the in-memory representation of a cached answer is carrying weight it doesn’t need. Combined, the five changes cut the per-entry memory footprint by 56% and freed roughly 100 terabytes of memory fleet-wide, Cloudflare says: enough to match the RAM capacity of about 130 of its Gen 13 servers.
What a Cache Entry Actually Holds
Before getting into the optimizations, it helps to know what Big Pineapple caches in the first place. A cache key identifies a query by its domain name (qname), record type (qtype), whether the response is authenticated, and a tag. The stored value is a CacheEntry: a timestamp, an internal Instant marking when the entry was inserted, a TTL, a hit counter, three separate lists of DNS records (answer, authority, and additional sections), and a list of any extended DNS errors attached to the response.
That structure is simple enough to reason about. It is also, as the rest of the post shows, full of small inefficiencies that only become visible once you multiply them by 250 billion.
Benchmarking Real Traffic, Not a Synthetic Load
To measure the impact of each change, Cloudflare replayed production-shaped traffic (56% A records, 25% AAAA, 19% TXT, with TXT payloads ranging from 64 to 224 bytes and each entry holding one to four records) through a custom allocator wrapped around Rust’s system allocator. That allocator tracked bytes allocated per entry alongside insert throughput and lookup latency, which let the team attribute memory savings and speed changes to each individual optimization rather than to the rewrite as a whole.
Five Changes, Each Attacking a Different Kind of Waste
The Capacity Field Nobody Needed
Rust’s own documentation describes Vec<T> as a “(pointer, capacity, length) triplet”: three machine words, 24 bytes on a 64-bit system. The capacity field exists so a vector can grow without reallocating on every push. But a cached DNS response never changes after it is inserted. There is no growing to plan for.
Cloudflare swapped Vec<T> for Box<[T]> (and String for Box<str>) wherever a field is set once and never mutated again. A boxed slice is what Rust’s own Box documentation treats as a “fat pointer,” just a pointer and a length: 16 bytes instead of 24, because it drops capacity tracking entirely once a value moves from a growable, owned vector into a fixed-size boxed slice. With eight such fields per cache entry, that alone saves 64 bytes before counting the excess heap space a Vec would otherwise have over-allocated. Across the fleet, this single change freed more than 15 terabytes.
Three Lists Became One
A DNS response can carry records in three sections: answer, authority, and additional. Big Pineapple used to store each section as its own list, meaning three separate pointers and three separate lengths. The rewrite stores every record in one list and marks where each section ends using two 16-bit offsets. That replaces two 8-byte pointers and two 8-byte lengths with two 2-byte integers, a saving of 28 bytes per entry, and it also lets Rust’s struct-padding rules shrink the entry further: removing small fields can eliminate alignment padding that would otherwise sit unused between them.
Inferring the Owner Instead of Storing It
Most DNS records have an owner name identical to the domain that was queried. Storing that owner explicitly on every single record is redundant almost all of the time. Big Pineapple now stores the owner as an Option<Box<Name>>: None when the owner matches the query name, the common case, inferred at read time instead of stored on disk in RAM; Some only when a record genuinely has a different owner, such as a CNAME target. That avoids a heap allocation for most records without losing accuracy for the ones that actually need it.
An Enum Sized for Its Biggest Member
Rust enums are sized to fit their largest variant. Big Pineapple’s RecordData enum covers everything from a 4-byte A record to a 136-byte NAPTR record, which meant every variant, including the tiny ones, was padded out to 144 bytes to accommodate the rare large ones. Since A and AAAA records make up more than 80% of the traffic Cloudflare sees, the large majority of cached records were wasting more than 120 bytes each just to leave room for record types they were not.
Boxing the Big Variants, and What That Costs
The obvious fix is to move the larger variants onto the heap and store an 8-byte pointer in their place, letting small variants like A and AAAA stay inline. That recovers the 120 wasted bytes on common records, but it is not free: boxed data scatters across the heap instead of sitting next to the rest of the entry, so reading it back costs an extra CPU cache-line fetch, and jemalloc’s own size-class rounding, a well-documented allocator behavior that rounds allocation requests up to fixed buckets, adds allocator overhead on top. Cloudflare is upfront that boxing traded one kind of waste for a smaller, different one.
Skipping the Struct Entirely
The fifth change goes further than boxing: instead of representing each record as a typed Rust enum at all, Big Pineapple now stores most records as raw bytes in a single Box<[u8]>, with a 2-byte length prefix marking where each record starts and ends in wire format. For the record types that make up most traffic (A, AAAA, TXT, and DNSSEC-related records), that means Big Pineapple can copy bytes directly from the cached buffer into an outgoing response without re-serializing anything at all. Combined with the better memory locality of storing everything contiguously, Cloudflare says that cut cache lookup latency by 5% in its own benchmarks. Records are also assembled once into a reusable scratch buffer and then written into a single allocation instead of one per record, a change Cloudflare says increased cache insert throughput by 13% on its own, before counting the other four optimizations. The tradeoff is that reading one record back out requires walking the list sequentially instead of indexing into it directly, but with only one to four records per entry in practice, Cloudflare calls that cost negligible.
What Five Small Changes Added Up To
Individually, none of these changes sound dramatic: a dropped capacity field, a merged list, an inferred owner, a repacked enum, a boxed pointer, a wire-format byte buffer. Measured together against the same benchmark, they cut the average per-entry memory footprint from 953 bytes to 420 bytes, a 56% reduction, and per-entry allocations from about 1.1 kilobytes to 461 bytes, a 58% reduction. Insert throughput rose from roughly 625,000 to 893,000 entries per second, up 43%, and lookup latency dropped from 828 nanoseconds to 670 nanoseconds, down 19%. None of the memory savings came at the cost of speed; Cloudflare’s own numbers show both improved together.
On real Big Pineapple instances, per-instance memory usage fell across the board: p99 usage dropped from 9.3 GB to 5.3 GB, a 43% reduction, and p90 dropped from 6.5 GB to 3.8 GB, a 42% reduction. Fleet-wide, Cloudflare says the changes freed roughly 100 terabytes of memory, which it equates to the RAM capacity of about 130 of its Gen 13 servers.
Why a Struct Layout Rewrite Is Worth a Blog Post
Few engineering teams operate anything close to 250 billion of anything, which is exactly why this kind of optimization rarely gets attention outside hyperscale operators. At a normal service’s scale, an extra Vec field or an oversized enum variant is invisible; the gap between 953 bytes and 420 bytes per entry does not show up on anyone’s dashboard until the entry count reaches into the hundreds of billions. Cloudflare’s five changes are also a reminder that in a systems language like Rust, the type system does not just describe data, it dictates memory layout, and every convenience field (a capacity you never use, an owner you could infer, an enum variant you rarely hit) carries a per-instance cost that compounds with scale. The same underlying principles (avoid unused capacity, merge redundant metadata, do not pad the common case for the rare one, weigh boxing against memory locality) apply well below Cloudflare’s scale. They just do not earn a blog post until the multiplier gets large enough.








No Comment! Be the first one.