1.1.1.1のDNSキャッシュを最適化して100テラバイトのメモリを節約した話
Saving 100 terabytes of memory by optimizing 1.1.1.1's DNS cache
Saving 100 terabytes of memory by optimizing 1.1.1.1's DNS cache
CloudflareのDNSリゾルバー「1.1.1.1」において、DNSキャッシュの仕組みを最適化することで、驚異の100テラバイトものメモリを削減したエンジニアリングの舞台裏を解説します。大規模トラフィックをさばくインフラ設計におけるメモリ管理の重要性と、具体的な改善アプローチについて詳しく掘り下げた技術レポートです。
これぞシステムプログラミングが今でも重要である理由だね。CacheEntryメンバーの直後にレコードデータを配置してメモリを個別に確保しないという、自明な最適化が漏れているように見える。まあ、これはCプログラマとしての意見であって、Rustではそれほど簡単ではないのかもしれないけれど。
自分自身のMaraDNSでは、ブラックリストエントリのメモリ使用量を劇的に最適化するために、一つの巨大なmalloc()でエントリ用のメモリを確保し、そのメモリブロックを走査してブラックリスト対象を探す手法をとったよ。エントリごとにmalloc()を使っていたときは大きなブラックリストで237MBものメモリを消費していたけど、一つのmalloc()呼び出しで読み込むように最適化したら、たったの9.5MBまで減ったんだ。https://samboy.github.io/blog/entries/MaraDNS.html#BlogEntry... (https://samboy.github.io/blog/entries/MaraDNS.html#BlogEntry-2022-12-28)
どれもメモリ使用量を減らすためのかなり標準的なアプローチだね。ただ、複数のリストを一つに結合するという手法は、Rustの安全性の保証を損なっているんじゃないかと思えてならない。以前は別々のVecオブジェクトだったから、Rustが範囲外アクセスを防いでくれていた。でも、それらを一つのVecにまとめてオフセットに頼るようになると、サブスライスで範囲外アクセスが起きてもパニック(エラー停止)しなくなってしまう可能性がある。些細な点だし最適化が無効になるわけではないけれど、記事でその点に触れていないのは意外だった。
MacBookのDNSサーバーを1.1.1.1や8.8.8.8に上書きしていると、公共Wi-Fiで問題が起きることがある。これはキャプティブポータルがcaptive.apple.comの独自解決を要求するからだと思う。外部のDNSサーバーだと、ローカルゲートウェイの認証ページを正しく解決できないんだろうね。
これほど自明な最適化に時間がかかったのは不思議だけど、単に他の部分の最適化で手一杯だっただけかもしれないな。
率直に言って、そもそも何でこんなことに高レベルなコンテナを使っていたのかが謎。あと、この一文も気になった。「Big Pineappleはマルチスレッドで高頻度な割り当てワークロード向けに設計されたアロケータであるjemallocを使っている」。jemallocのマルチスレッド性能は、他のモダンなアロケータと比べると実はそこまで良くないから、選定理由が謎なんだよね。それよりも、なぜmmapでMAP_ANON | MAP_NORESERVEを使ってアリーナを切り出す手法と比較して、アロケータを使おうとしているのかがもっと謎。そういうやり方なら型変換のハックもできるしね。そもそもRustでそれがサポートされているかは確信がないけれど。
一般的な話だけど、プログラミング言語のネイティブなメモリ内オブジェクトフォーマットは、通常ランダムアクセス、均一性、可変性(固定オフセットのフィールドなど)のために最適化されている。一方、ネットワークやディスク用のシリアライズフォーマットは、よりコンパクトに設計される傾向がある。ただ、自分が必要とする特性に合わせて、独自のメモリ内表現を設計することだってできるよ。
これこそが正しいソフトウェアの提供方法だよ。まずは動くプロダクトを作ってアイデアを検証し、ビジネスを安定させて利益を出し始めてから、コストの最適化を始めればいい。実際、最適化はこのプロセスの中で最も簡単な部類に入る。このHNのスレッドを見ても、こういった最適化を「些細なこと」と考えるシステムプログラミングの専門家がたくさんいるわけだし。
Record構造体にはrtypeとデータが含まれていて、RecordDataはタグ付き共用体になっているよね。これら二つって常に同期しているものじゃないの?DNSの専門家じゃないからただの疑問なんだけど、これって冗長なのか、それとも両方ある理由があるのかな。シリアライズ済みで保存されているならもう関係ない話だけど、なぜこういう設計だったのか興味がある。
Cloudflareで面白いのは、彼らのトップ1k〜10kのアドレスを使ったDNSワーミングスクリプトがあること。マスターが起動するとキャッシュ全体を温めてくれるんだ。他の部分はmemcacheを使っているから、クラスタは常にホカホカな状態。僕が知る限り、彼らほどドメイン統計を公開しているところはないよ。