Cloudflare가 퍼블릭 DNS 리졸버 '1.1.1.1' 등에서 사용되는 DNS 캐시의 데이터 구조를 재검토하여 시스템 전체에서 약 100TB의 메모리를 절감했다고 발표했습니다. 벤치마크에서는 캐시 1건당 메모리 사용량이 953바이트에서 420바이트로 56% 감소했으며, 메모리를 줄이면서도 캐시 쓰기 및 읽기도 빨라졌다고 합니다. How we saved 100 terabytes of memory by optimizing 1.1.1.1’s DNS cache | Cloudflare Bloghttps://blog.cloudflare.com/dns-cache-memory-optimization-1111/
1.1.1.1은 도메인 이름을 IP 주소로 변환하는 Cloudflare의 퍼블릭 DNS 리졸버입니다. 예를 들어 브라우저에 'cloudflare.com'을 입력하면 단말기는 DNS 리졸버에 문의하여 연결 대상 IP 주소를 확인합니다. 1.1.1.1에서는 한 번 획득한 DNS 응답을 캐시해 두고, 같은 문의가 오면 상위 DNS 서버에 매번 문의하지 않아도 답변할 수 있도록 하고 있습니다. Cloudflare에서 1.1.1.1을 지원하는 DNS 기반이 'Big Pineapple'입니다. Big Pineapple은 1.1.1.1 외에도 Gateway DNS나 DNS Firewall 등 여러 서비스에서 사용되며, 항상 2500억 건이 넘는 DNS 캐시 엔트리를 보유하고 있습니다. 2500억 건이라는 규모가 되면 캐시 1건당 단 1바이트만 추가로 사용해도 Cloudflare 전체에서는 250GB 이상의 메모리가 필요하게 되는 계산입니다.
그래서 Cloudflare는 캐시에 저장하는 정보를 하나씩 조사하여 저장 후에는 불필요해지는 영역이나 중복되는 정보를 줄여 나갔습니다. 가장 먼저 손을 댄 것은 Rust의 'Vec'와 'String'입니다. Vec는 데이터 본체에 대한 포인터와 현재 길이에 더해, 향후 데이터를 추가할 수 있도록 용량 정보도 보유합니다. 그러나 DNS 응답은 캐시에 저장한 후 내용을 변경하지 않기 때문에 추가용 용량은 필요 없습니다.
Cloudflare는 고정 크기로 저장할 수 있는 Box 계열 타입으로 교체하여 캐시 1건에 포함된 8개의 Vec와 String에서 총 64바이트를 절감했습니다. 여분으로 확보되어 있던 영역까지 합치면 2500억 건 이상의 캐시 전체에서 15TB 이상을 절약할 수 있었다고 합니다.
DNS 응답을 저장하는 방법에도 세세한 낭비가 있었습니다. DNS 응답에는 답변, 권위 정보, 추가 정보 등의 여러 구분이 있지만, 기존에는 구분별로 별도의 리스트를 준비하고 있었습니다. Cloudflare는 3개 구분을 1개 리스트로 묶고, 구분의 경계를 나타내는 2개의 위치 정보를 각각 2바이트로 기록하는 방식으로 변경했습니다. 캐시 1건당 28바이트를 줄였습니다. 여러 불리언 값도 비트 단위로 묶고, Rust가 메모리상 위치를 맞추기 위해 삽입하는 여백도 줄였습니다.
또한 DNS 레코드에는 일반적으로 해당 레코드가 속한 도메인 이름이 저장되어 있습니다. 그러나 실제로는 문의한 도메인 이름과 레코드 측의 도메인 이름이 동일한 경우가 대부분입니다. Big Pineapple은 같은 이름을 이중으로 저장하지 않고, 필요해졌을 때 캐시의 검색 키에서 복원하는 방식으로 변경했습니다. CNAME 등으로 인해 다른 도메인 이름이 필요한 경우에만 전체 이름을 저장합니다.
Rust에서 DNS 레코드 종류를 다루는 방식도 재검토되었습니다. 기존에는 DNS 레코드 종류를 Rust의 enum이라고 불리는 열거형으로 관리하고 있었으며, 작은 A 레코드나 AAAA 레코드도 가장 큰 NAPTR에 맞춰 144바이트 영역을 사용하고 있었습니다. A 레코드에 필요한 데이터는 4바이트, AAAA 레코드도 16바이트로, 둘 다 Cloudflare DNS 트래픽의 80% 이상을 차지합니다. 크기가 큰 종류의 데이터만 별도의 메모리 영역으로 옮김으로써 A 레코드와 AAAA 레코드에서는 건당 120바이트를 절약할 수 있었습니다.
다만 데이터를 별도의 영역으로 옮기면 메모리 할당 횟수가 늘어나고, CPU가 떨어진 곳에 있는 데이터를 연속적으로 읽어 들여야 합니다. 그래서 최종적으로는 DNS 레코드의 데이터 부분을 통신 시 형식으로 인코딩한 바이트열로 저장하고, 하나의 연속된 영역에 모아서 저장하는 방식을 채택했습니다. 메모리상에 데이터가 모여 있기 때문에 읽기가 쉬워졌고, 많은 DNS 레코드에서는 응답을 만들 때 바이트열을 그대로 복사할 수 있습니다. Cloudflare의 벤치마크에서는 변경으로 캐시 검색에 걸리는 시간이 5% 단축되었고, 재사용 가능한 임시 버퍼를 사용하는 방식에서는 초당 캐시에 추가할 수 있는 건수가 13% 향상되었다고 합니다.
5단계 최적화를 합치면 캐시 1건의 메모리 사용량이 953바이트에서 420바이트로 감소하고, 할당하는 메모리 양도 1.1KB에서 461바이트로 줄었습니다. 실제 서버에서는 캐시 외에도 메모리를 사용하기 때문에 감소율은 작아지지만, 메모리 사용량의 99퍼센타일(p99)에서는 상주 메모리가 9.3GB에서 5.3GB로 43% 감소했습니다. Big Pineapple을 가동하는 서버군 전체에서는 약 100TB로, Cloudflare의 13세대 서버 약 130대분의 RAM에 해당하는 메모리를 확보할 수 있었다고 합니다. 또한 캐시 쓰기 성능은 43% 향상되었고, 검색 시간도 19% 단축되었습니다.
Cloudflare는 확보한 약 100TB를 DNS 캐시 용량 확대에 투입하여, 메모리 사용량을 늘리지 않고 저장할 수 있는 응답을 늘릴 계획입니다. 캐시에 남길 수 있는 응답이 늘어나면 상위 DNS 서버에 문의하는 횟수도 줄일 수 있다고 보고 있으며, Big Pineapple의 캐시에 대한 추가 최적화도 검토 중이라고 합니다.
원문 보기 | 출처: Gigazine