
2026/08/28 2:17
1.1.1.1 の DNS キャッシュ最適化でメモリ使用量を 100TB 削減
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Big Pineapple は、5 つの主要なストレージ最適化によりパーエントリーフッタープリントを 50% 以上削減したことで、DNS インフラストラクチャを大幅に革新することに成功しました。
Vec と String フィールドをボックスポインタに置換し、レコードセクションを単一のリストへ統合し、不要なオーナーフィールドを除去し、大きな列挙型バリアントをボックス化してパディングを削減し、レコードを連続的なワイヤフォーマットバッファに格納することで、膨大な量の無駄なヒープ容量を排除しました。これらの改善は、特に 1 クライアントネットワークにつき複数の照会バージョンをキャッシュする必要がある ECS クライアントサブネット(ECS)照会を処理する場所にとって極めて重要です。2026 年 5 月 18 日から 7 月 6 日までのプロダクションロールアウト後、システムは劇的な効率向上を達成しました:平均居住メモリは 9.3 GB から 5.3 GB に低下し(p99 使用量の 43% 削減)、実質的に Gen 13 サーバー 130 機以上の RAM を解放しました。また、挿通スループットが 43% 増加し、照会遅延は 19% 減少することで、高負荷下でも船団がより信頼性高く動作すると同時に、大きなコスト削減を実現しました。本文
BigPineapple: DNS キャッシュのメモリ使用量 56% 削減とパフォーマンス向上
BigPineapple(ビッグパイナップル)は、Cloudflare の1.1.1.1、ゲートウェイ DNS、DNS ファイアウォール、AS112 など複数の DNS サービスを支えるプラットフォームです。
- いかなる時点においても 2,500 億を超える DNS キャッシュエントリを保持しています。
- この規模において、1 エントリあたり 1 バイトの無駄は艦隊全体で約 250 ギガバイトのメモリ浪費に相当します。
🚀 達成された成果
連続して行われた 5 つの最適化変更により、以下の成果を達成しました。
- フットプリント削減: 1 エントリあたりのメモリ使用量を 50% 以上削減。
- メモリ確保: 艦隊全体で約 100 テラバイトのメモリが解放されました(Gen 13 サーバー 130 機分の RAM に相当)。
- パフォーマンス向上:
- インサートスループットは 43% 増加。
- 検索遅延は 19% 減少。
1. キャッシュ構造と ECS の影響
キャッシュの基本動作
- 起動時:空のキャッシュから開始。
- 埋まり方:DNS クエリに応じて埋まり、上限に達すると古いものや人気の低いものを弾き出します。
- ECS(EDNS Client Subnet)の影響:
- オーソライズサーバーはクライアントのネットワークにより異なる回答を返すため、同じクエリの複数バージョンをキャッシュする必要があります。
- これによりエントリ数と各エントリのメモリ消費が増加し、最適化の効果が高まります。
データ構造の詳細
キャッシュ内のアイテムは**鍵(Key)と値(Value)**のペアです。
// キャッシュキー:何が検索されたかを示します pub struct CacheKey { qname: Name, qtype: Rtype, authenticated: bool, tag: Vec<u8>, } // キャッシュエントリ:DNS 応答とメタデータを格納します pub struct CacheEntry { timestamp: UnixTimeStamp, pub inception: Instant, pub ttl: Ttl, pub hits: u32, pub answers: Vec<Record>, pub authority: Vec<Record>, pub additional: Vec<Record>, pub errors: Vec<ExtendedError>, // ... }
課題: 格納後に不要なフィールドや、不必要に重い型を使用している箇所への改善余地がありました。
2. Vec<T>
と String
の容量削減
Vec<T>Stringベンチマーク設定
- 実環境のトラフィック分布と類似したランダムエントリで充填してベンチマーク。
- レコード構成:A(56%) / AAAA(25%) / TXT(19%)。
- TXT レコードは平均応答サイズ(64〜224 バイト)を模擬。
課題:Vec
と String
の過剰予約
VecStringVec<T> や String は、現在の長さだけでなく総容量とヒープへのポインタを持ちます。
- キャッシュに格納後はアイテム追加が停止するため、容量フィールドは役に立ちません。
- 例:「8 つのアイテム用の容量」を持つが実際は「5 つ」だけの場合、3 つのスロットが無駄になります。
解決策:Box<[T]>
と Box<str>
の採用
Box<[T]>Box<str>作成後には成長しないため、容量予約スペースを削除できます。
- 節約効果:
- フィールドあたり 8 バイト、エントリあたり 64 バイトの節約。
- 過剰なヒープメモリ予約がなくなります。
- 累積効果: 2,500 億エントリにわたり、合計で 15 テラバイト以上のメモリ節約を実現。
3. リストとポインタの削減
セクションリストの最適化
回答、オーソリティ、追加セクションを別々のリストではなく、単一のリスト+オフセットで管理しました。
- DNS レコード数は
(2 バイト) で収まるため、個別のリストより短くなります。u16 - 節約効果: エントリあたり 28 バイト(個別リストのポインタ 8 バイト × 2 + 長さフィールド)を削減。
パディング削減
Rust は構造体のサイズを整列倍に丸めるため、パディングが発生しますが、不要なフィールド削除によりこれを回避できました。
- 例:複数の
フィールドを単一の bitflag にパック化し、周囲のパディング削減を実現。bool
4. 「所有者」フィールドの廃棄
DNS レコードの「所有者」
レコードが属するドメインを示すフィールドですが、多くの場合は検索されたドメインと同一です。
;; 例:example.com A へのクエリ ;; ANSWER SECTION: example.com. 300 IN A 198.51.100.1 example.com. 300 IN A 198.51.100.2 ;; 例:CNAME の場合(所有者が異なる) ;; ANSWER SECTION: example.com. 300 IN CNAME cdn.example.com. cdn.example.com. 300 IN A 198.51.100.1
Wire Format とキャッシュのトレードオフ
- Wire Format: RFC 1035 に従い、重複ドメインは名前圧縮でポインタ化されます。
- キャッシュ: 検索時の高速化(ホットパス)のために、通常は完全な所有者名を保存します(メモリ対速度のトレードオフ)。
解決策:条件付き保存
大多数のレコードは検索されたドメインと同じ所有者を持つため、これを廃棄し読み取り時に復元するようにしました。
pub struct Record { owner: Option<Box<Name>>, // None: ヒープ割り当てなし (同一所有者) // Some: ヒープ割り当てあり (異なる所有者) class: Class, ttl: Ttl, rtype: Rtype, data: RecordData, }
- 効果: 検索されたドメインと同じ所有者を持つレコードの大半において、ヒープ割り当てを回避しました。
5. Enum のサイズ化と Box 化
課題:Enum のパディング問題
Rust の
enum は常に最大のバリエーション分のメモリを確保します。
- 例:
(136 バイト) が最大なら、A レコード (4 バイト) も 136 バイト+タグ+パディング(計 144 バイト)になります。NAPTR - A/AAAA がトラフィックの 80% を超えるため、パディングによる無駄が巨大に累積します。
解決策:Box で包装
大きなバリエーションを別々のヒープ割り当て (
Box) に移動させ、Enum自体は最小化しました。
pub enum RecordData { A(Ipv4Addr), // インライン (4 バイト) Aaaa(Ipv6Addr), // インライン (16 バイト) Txt(Box<Txt>), // ヒープに移動 (8 バイトポインタのみ) Naptr(Box<Naptr>), // ヒープに移動 Svcb(Box<Svcb>), // ヒープに移動 }
- 効果: A/AAAA レコードあたり 120 バイトの節約。
Box 化によるコストと対策
Box 化には以下のコストが発生しますが、全体最適で削減可能です。
- アロケーターオーバーヘッド:
- 最小サイズクラスに丸め上げられるため(例:TXT は OK, MX は 40→48 バイト丸め)、細かな無駄が発生します。
- メモリアクセス Locality の低下:
- データがヒープ上で散らばるため、CPU キャッシュ効率が悪化します。
対策: 後述の「ワイアフォーマットでの保存」により、これらのコストを相殺し、さらにパフォーマンス向上を実現しました。
6. ワイアフォーマットでレコードを保存する(最終最適化)
従来の問題点
- 完全な DNS メッセージ(Wire Format)を保存すると:
- DNSSEC レコードは
フラグの有無でバージョンが分岐するため、2 つのバリエーションが必要。DO - 毎回の検索でメッセージをパースするオーバーヘッドが発生。
- DNSSEC レコードは
解決策:生バイト+構造化フィールドハイブリッド
- レコードデータを**生バイト (
)**として保存し、他のメタデータは構造化フィールドのままにしました。Box<[u8]> - メリット:
- Enum オーバーヘッドと Box ヒープ割り当ての排除。
- データの連続配置による CPU キャッシュ Locality の改善。
- 検索パスの高速化:
- 直近の最適化により、A/AAAA/TXT/DNSSEC レコードのエンコード済みバイトを直接コピー可能に。
- CNAME/Native など解析が必要なレコードは一部のみ該当し、全体としては負荷削減。
インサート時のメモリー管理
- 再利用可能なスクラッチスペースバッファへ書き込み。
- サイズが確定したら
を割り当ててBox<[u8]>
(1 つの大きなアロケータに集約)。memcpy - 効果: ベンチマークでキャッシュインサートスループットを**+13%**向上。
7. 結果:実環境での測定とパフォーマンス
メモリ使用量の削減(P99/P98/P99)
- p99 メモリ: 9.3 GB → 5.3 GB (-42%)
- 実在メモリ (Resident Memory): **-43%**削減。
- キャッシュが満杯に近いほど、絶対的な節約量が大きくなりました。
エントリあたりのフットプリント比較
| メトリック | Before | After | 変化率 |
|---|---|---|---|
| エントリあたりのネットフットプリント | 953 バイト | 420 バイト | -56% |
| エントリあたりの割り当て | 1.1 KB | 461 バイト | -58% |
注:ベンチマークでは 953→420 バイト(-56%)の削減ですが、実在メモリへ反映されたのはプロセス全体の特性によるものです。それでも艦隊全体で約100 テラバイトの集計ワーキングセットメモリが低減されました。
パフォーマンス向上
- キャッシュインサートスループット: +43% (625,000 → 893,000 entries/s)
- 検索遅延: -19% (828 ns → 670 ns)
今後の計画とコミュニティ
- 再投資: 解放されたメモリをキャッシュ容量増加分に投入し、ヒットレートを高めアップストリームクエリボリュームを削減。
- さらにの最適化: キャッシュ自体のさらなる改良を検討中。
関連リンク