× Install ThecoreGrid App
Tap below and select "Add to Home Screen" for full-screen experience.
B2B Engineering Insights & Architectural Teardowns

DNS cache memory: 5 оптимизаций в Big Pineapple

DNS cache memory в масштабе Cloudflare — это не вопрос элегантности. Это вопрос каждого байта, каждой аллокации и каждой cache line. В Big Pineapple пять изменений в структуре хранения сократили per-entry footprint более чем на 50% и одновременно улучшили поведение lookup.

Big Pineapple — платформа, лежащая в основе 1.1.1.1, Gateway DNS, DNS Firewall, AS112 и других DNS-сервисов — хранит более 250 миллиардов cache entries. При таком масштабе даже один лишний байт на запись превращается в существенные затраты памяти на уровне всего fleet. Особенно остро проблема проявляется в ECS-heavy locations, где один и тот же запрос может порождать несколько закэшированных ответов для разных клиентских сетей. Это увеличивает и количество записей, и per-entry memory pressure, поэтому сама структура cache layout начинает напрямую влиять на стоимость системы.

Основная проблема формулировалась просто, но игнорировать её было уже невозможно: cache платил за гибкость, которая ему не нужна. DNS entries записываются один раз, а затем многократно читаются, поэтому любое поле, полезное для изменения данных, но не нужное для lookup, становится overhead. При этом cache должен был сохранять производительность на реальном traffic mix, а не просто показывать хорошие результаты в benchmark. Поэтому каждая оптимизация должна была отвечать на один и тот же вопрос: можно ли уменьшить потребление памяти, не замедлив hot path?

Первым шагом стало устранение overhead контейнеров, рассчитанных на рост. Vec и String хранят capacity fields и резервируют пространство для будущего расширения, однако cache entries после вставки неизменяемы. Замена их на Box<[T]> и Box позволила убрать как metadata для capacity, так и неиспользуемый heap slack. Затем команда объединила несколько списков записей в один список с offsets и упаковала boolean values в bitflags, чтобы уменьшить padding, возникающий из-за alignment. Это классический systems trade-off: меньше структурной гибкости в обмен на более плотное хранение данных и лучшую locality.

Более глубокое изменение касалось самого способа представления DNS record data. Вместо хранения каждой записи в виде крупного enum variant самые большие варианты сначала поместили в Box, а затем перенесли данные в единый contiguous raw byte buffer. Это позволило сократить padding, уменьшить количество отдельных heap allocations для вариантов и улучшить cache locality. Кроме того, система получила возможность напрямую копировать многие типы записей в исходящий DNS response вместо того, чтобы заново собирать их поле за полем.

Компромисс был осознанным. Cache отказался от random access внутри набора записей и перешёл к sequential iteration, поскольку количество записей в одной cache entry невелико, а выигрыш в памяти имеет большее значение.

Ещё одной важной оптимизацией стал отказ от хранения owner name, если он совпадает с запрошенным доменом. В распространённом случае owner можно восстановить из cache key непосредственно во время lookup. Это делает entry менее самодостаточной, однако key уже присутствует на read path, поэтому такой подход позволяет избежать heap allocation в наиболее частом сценарии. Если owner отличается — например, при CNAME chains — полное имя по-прежнему сохраняется. Та же логика прослеживается во всей архитектуре: дорогое представление данных нужно хранить только тогда, когда оно действительно необходимо.

Реализация тщательно измерялась. В benchmark использовались случайно сгенерированные cache entries, приближенные к production traffic: 56% A records, 25% AAAA и 19% TXT, от одной до четырёх записей на entry. Специальный allocator оборачивал Rust System allocator, чтобы отслеживать количество и размер allocations для каждой cache entry. Измерялись insert throughput и lookup latency на всём cache flow, а во время rollout дополнительно отслеживалась production resident memory, поскольку потребление памяти процессом определяется не только cache.

Это разделение важно: benchmarks показывают, откуда берётся экономия, а production показывает, сохраняется ли она при взаимодействии с остальной системой.

Результаты оказались существенными и достигались поэтапно, а не одним большим снижением. На уровне всего fleet оптимизации освободили около 100 TB памяти, что эквивалентно объёму RAM примерно в 130 Gen 13 servers.

Per-entry footprint снизился с 953 до 420 bytes — на 56%. Количество памяти, выделяемой на entry, уменьшилось с 1,1 KB до 461 bytes. При этом insert throughput вырос на 43% — с 625 000 до 893 000 entries/s, а lookup latency снизилась на 19% — с 828 ns до 670 ns.

Ключевой момент здесь в том, что команда не пожертвовала скоростью ради экономии памяти. Меньшее количество allocations и лучшая memory locality одновременно улучшили оба показателя.

Архитектурный вывод здесь довольно прагматичен. В таком масштабе cache — это уже не просто data structure. Он является частью cost model сервиса. Правильная архитектура должна соответствовать природе immutable data, сохранять locality на hot path и использовать heap memory только там, где этого действительно требует протокол.

В Big Pineapple такой подход дал существенное сокращение resident memory и одновременно создал возможность в дальнейшем увеличить cache capacity без соответствующего роста общего потребления памяти.

Ознакомиться с источником

×

🚀 Deploy the Blocks

Controls: ← → to move, ↑ to rotate, ↓ to drop.
Mobile: use buttons below.