Cloudflare DNS 캐시 100TB 줄인 비결

DNS 캐시 항목 하나에서 불필요한 몇 바이트를 줄이면 얼마나 큰 효과가 날까요? 2,500억 개가 넘는 항목을 다루는 환경에서는 단 1바이트도 전체 시스템의 250GB로 커져요.
1. 1.1.1.1 DNS 캐시와 메모리 문제
Cloudflare의 Big Pineapple은 1.1.1.1, Gateway DNS, DNS Firewall 등 여러 DNS 서비스를 지원하며, 항상 2,500억 개 이상의 캐시 항목을 저장해요.
캐시는 DNS 요청이 들어올 때마다 채워지고, 최대 항목 수에 도달하면 오래되거나 사용 빈도가 낮은 데이터를 제거해요. 특히 ECS(클라이언트 네트워크에 따라 다른 DNS 응답을 제공하는 기능)를 사용하는 지역은 같은 질의도 여러 버전으로 저장하므로 메모리 부담이 더 커집니다.
Cloudflare는 실제 트래픽과 비슷하도록 A 레코드 56%, AAAA 25%, TXT 19%로 벤치마크했어요. 사용자 정의 할당자(메모리 할당을 추적하는 도구)로 항목별 메모리와 할당 횟수뿐 아니라 삽입 처리량, 조회 지연 시간까지 함께 측정했습니다.
2. Vec를 Box로 바꿔 낭비 제거
첫 번째 개선은 저장된 뒤 변경되지 않는 데이터에 불필요한 가변 자료구조를 사용하지 않는 것이었어요. Vec<T>는 포인터와 길이 외에도 향후 확장을 위한 용량 정보를 보관합니다.
하지만 DNS 응답은 캐시에 들어간 이후 수정되지 않기 때문에 용량 필드와 여분 공간이 필요하지 않아요. Cloudflare는 Vec<T>를 Box<[T]>로, String을 Box<str>로 변경해 필드마다 8바이트를 줄였습니다.
캐시 항목에는 이런 필드가 8개 있어 항목당 64바이트, 전체적으로는 15TB 이상의 메모리를 절감했어요. 읽기 전용 목록이나 확장되지 않는 설정 데이터를 저장하는 서버라면 동일한 방식을 검토할 수 있습니다.

3. DNS 섹션과 중복 도메인 압축
두 번째 핵심은 목록과 포인터의 수를 줄이는 것이었어요. answer, authority, additional 섹션을 각각 저장하는 대신 하나의 목록에 합치고, 각 섹션의 시작 위치만 u16 오프셋으로 기록했습니다.
이 방식은 포인터와 길이를 가진 목록 두 개를 제거해 항목당 28바이트를 추가로 절약해요. 여러 불리언 값도 하나의 비트 플래그(여러 상태를 비트 단위로 저장하는 방식)로 묶어 Rust 구조체의 정렬 패딩까지 줄였습니다.
DNS 레코드의 소유자 도메인이 질의 도메인과 같을 때는 이름도 중복 저장하지 않았어요. owner를 Option<Box<Name>>으로 두고 값이 없으면 캐시 키에서 복원함으로써, 대부분의 레코드에서 별도의 힙 할당을 제거했습니다.
4. Rust enum의 숨은 메모리 비용
Rust의 enum은 가장 큰 variant(열거형의 개별 유형)에 맞춰 전체 크기가 정해져요. Cloudflare의 DNS 레코드 열거형은 NAPTR 때문에 144바이트였지만, 트래픽의 80% 이상을 차지하는 A와 AAAA는 각각 4바이트와 16바이트만 필요했습니다.
처음에는 큰 TXT, NAPTR, SVCB 레코드만 Box로 옮겨 A와 AAAA에서 레코드당 120바이트를 절약했어요. 다만 개별 Box는 할당자 크기 반올림과 포인터 추적을 발생시켜 CPU 캐시 지역성(연속된 데이터를 빠르게 읽는 특성)을 떨어뜨렸습니다.
따라서 작은 데이터가 압도적으로 많은 enum을 설계할 때는 가장 드문 대형 variant가 전체 크기를 키우지 않는지 확인해야 해요. 네트워크 패킷, 이벤트 로그, 메시지 큐처럼 유형별 크기 차이가 큰 시스템에서 특히 유용한 점검 기준입니다.
5. 레코드를 Wire Format으로 연속 저장
최종적으로 Cloudflare는 개별 레코드를 파싱된 열거형이 아닌 연속된 바이트 버퍼로 저장했어요. 각 레코드는 2바이트 길이 정보와 원시 데이터로 구성되고, 전체 데이터는 하나의 Box<[u8]>에 담깁니다.
완성된 DNS 메시지 전체를 저장하지 않은 이유도 있어요. 클라이언트의 DNSSEC 요청 여부에 따라 응답이 달라지고, 매번 전체 메시지를 다시 파싱해야 하는 문제가 있기 때문입니다.
새 방식은 A, AAAA, TXT와 DNSSEC 레코드를 응답 버퍼로 바로 복사할 수 있어 직렬화 작업도 줄였어요. 재사용 가능한 임시 버퍼를 활용한 결과 이 변경만으로 조회 지연 시간은 5% 감소하고, 캐시 삽입 처리량은 13% 증가했습니다.

6. 100TB 절감이 만든 최종 결과
다섯 가지 최적화를 적용한 결과 항목당 메모리 사용량은 953바이트에서 420바이트로 56% 감소했어요. 항목별 할당량도 1.1KB에서 461바이트로 58% 줄었습니다.
- 삽입 처리량: 초당 62만5천 건에서 89만3천 건으로 43% 증가했어요.
- 조회 지연 시간: 828ns에서 670ns로 19% 감소했어요.
- 운영 메모리: p99 인스턴스는 9.3GB에서 5.3GB, p90은 6.5GB에서 3.8GB로 줄었어요.
- 전체 절감량: 약 100TB로, Cloudflare Gen 13 서버 130대의 RAM과 비슷한 규모예요.
Cloudflare는 확보한 메모리를 캐시 용량 확대에 다시 활용해 적중률을 높이고 상위 DNS 서버로 전달되는 질의를 줄일 계획입니다. 이번 사례는 대규모 시스템에서 성능 최적화가 거대한 재설계보다 자료구조의 작은 낭비를 찾아 제거하는 일에서 시작될 수 있음을 보여줘요.
운영 중인 서비스에서도 항목 수에 1바이트를 곱해 보세요. 평범해 보였던 Vec, String, enum 하나가 다음 최적화의 출발점일 수 있습니다.






