Mozilla Flux

Mozilla関係の情報に特化したブログです。

Firefox 32の新しいHTTPキャッシュ(cache v2)とは

構想から2年半以上をかけてリリース版へ

Firefox 32では、新しいHTTPキャッシュ(以下cache v2)が標準で有効化された。リリースノートでは、世代別GCの統合よりも上の、トップの項目に挙げられており、Mozillaがこの機能を重視していることがわかる。実際にも、特にAndroid版Firefoxを使っているとコンテンツの表示がスムーズになっているのを体感できる。

cache v2がブラウジングに与える影響は大きい。過去に閲覧したWebページを再度閲覧する場合、キャッシュの性能次第で表示の完了までにかかる時間が変わってくるのはもちろんだが、通常、Webページを閲覧した時点でディスク上にキャッシュが生成されるようになっているため、この処理が遅いとFirefox本体の動作の足を引っ張ることになる。

その影響の大きさゆえに、cache v2の設計と実装は慎重に進められた。最初に構想が明らかにされたのは、2012年1月25日(米国時間。以下同じ)、mozilla.dev.tech.networkの"Upcoming Cache Work"というスレッドにおいてである。そこでは、以下のようなコンセプトが示された。

  • ロックの競合を減らす。キャッシュ全体が1つの大きなロックによって保護されているのを、より粒度の細かなものとする。
  • メインスレッドとキャッシュスレッドの間でやりとりする時間を減らす。
  • キャッシュ内の並列性を高める。スレッドを2つ以上にしてI/O処理のボトルネックを解消する。
  • ディスクキャッシュのフォーマットを変更し、キャッシュの大規模な削除を少なくする。
  • クラッシュ後の再起動時にキャッシュ全体を削除しなくても済むようにする。
  • エントリをより効率的に保存する。エントリとメタデータを1つのファイルに統合する。

その後、MozillaWikiのNecko/Cache/Plansがいろいろと修正され、履歴によれば、cache v2の設計がほぼ固まったのは、2013年3月ころとみられる。デザインの目標から主なものを挙げてみよう。

  1. キャッシュのためのAPIをバージョン化し、アップデートを容易にする。
  2. すべてのAPIを非同期化し、メインスレッドのロックを防ぐ。
  3. 異常終了してもキャッシュ全体を破棄しない。
  4. gzip圧縮をサポート。
  5. ディスクキャッシュが完全に無効化されてもブラウザが正常に動作する。
  6. 通信待ちを減らす。

Firefox 27 Nightlyにcache v2が実装されたのは、2013年9月20日のことだ(Bug 913807)。この時点では、まだデフォルトでは無効化されていた。開発者のHonza Bambas氏は、mayhemer's blogの記事"Mozilla Firefox new HTTP cache is live!"の中で、クラッシュや(プロセスの)キル後もキャッシュエントリが維持され、UIのハングも起きないという点を強調していた。また、2013年第4四半期中にはデフォルトで有効化されるとも述べており、そのとおりであれば、2014年第1四半期か、遅くとも第2四半期の初めには、リリース版にcache v2が投入されていたはずだ。

ところが、cache v2に切り替えてもクラッシュが起きないようにするなど、仕上げに時間がかかったため、2013年12月10日時点で、デフォルト有効化の時期はFirefox 31へと延期された。しかも、この予定も若干ずれ込み、リリース版でも有効化されることになったのは、Firefox 32の開発サイクルに入った後の2014年5月16日であった(Bug 913806)。

Firefox 32が2014年9月2日にリリースされ、cache v2は、構想から実に2年半以上を経て一般ユーザーの手元に届くことになった。設計が固まってからでさえ、1年半近い期間が経過しており、大きなプロジェクトであったことがわかる。

cache v2の特長

実装されたcache v2がどのようなものか、mayhemer's blogの記事などを参照しつつ、より詳しく見ていこう。

処理の非同期化

"New Firefox HTTP cache backend, first impressions"によれば、cache v2において、ファイルを開き、データを読み出し、あるいは書き込むI/O処理は、単一のバックグラウンドスレッドで行われる。つまり、Firefox本体が動作するメインスレッドとは別のスレッドで動作するため、メインスレッドの処理はブロックされなくて済む。

また、"New Firefox HTTP cache backend - story continues"によれば、処理に優先度を付けてスケジューリングができるようにもなっている。たとえば、レンダリングをブロックするファイルを開いてデータを読み出す処理は最優先となる一方、書き込みは最後に行われるという。このように、キャッシュへの書き込み処理を後回しにできるため、初めて閲覧したWebページでも表示時間が短縮される。

ちなみに、Firefox 33では、ディスクへの書き込み未了のデータが一定サイズに達すると新たな書き込みを中止する仕組みが導入された(Bug 1013395)。ただし、HTML、CSS、フォント、JavaScriptはそれ以外のデータとは別枠になり、書き込みの優先度が高く設定されている。

フォーマットの変更と新しいインデックス

ディスクキャッシュのフォーマットも変更された。各URLごとに独立したファイルが、いかにサイズが小さくとも生成される。各ファイルは、自己コントロール用のハッシュを備えているため、fsync命令を使わなくても正確性をチェックできる。ファイルを独立させることで、クラッシュ時に破損する範囲を限定するようにもなっているとみられる。

また、多数のキャッシュエントリを管理するため、新しいインデックスの仕組みも導入されている(Bug 923016)。ファイルの探索に要する時間が短縮されているようだ。

データの先読み

"New Firefox HTTP cache now enabled on Nightly builds"によれば、cache v2にはデータを先読みする仕組みがある(Bug 913819)。画像などサイズの大きなコンテンツの読み込みが高速化される。先読みは、256KBのチャンクを単位として行われ(Bug 988318)、デフォルトでは4チャンクすなわち1MBのデータが先読みされる。もっとも、browser.cache.disk.preload_chunk_countの設定を4よりも大きくすれば、このデータ量は大きくなる。

Firefox 33では、ブラウザセッション中で使用済みのエントリについては、先読みのタイミングを早める調整が加えられており(Bug 1013587)、表示の高速化が期待できる。

メタデータ用のメモリプール

cache v2には、最近読み込まれたキャッシュエントリのメタデータ(レスポンスヘッダなど)を保管しておくメモリプールが用意されている(Bug 986179)。キャッシュスレッドによって管理されており、プール内にメタデータのあるキャッシュは即座に再利用される仕組みのようだ。プールのサイズは、デフォルトでは250KBとなっているが、browser.cache.disk.metadata_memory_limitの設定を250から増減させることによって変更できる。

メモリプールが一杯になったときは、古いデータから順に追い出される(パージされる)ことになる。デフォルトでは、6時間を経過したデータは古いものとして扱われるが、これもbrowser.cache.frecency_half_life_hoursの設定によって変更が可能だ(Bug 1012327)。なお、アクセス中のエントリを誤ってパージしない仕組みもある(Bug 942835)。

ちなみに、従来どおりメモリキャッシュも存在しており、こちらはbrowser.cache.memory.capacityの最適な設定が自動的に検出されるようになっている。

ページ読み込みの高速化

以上の機能が合わさることで、ページの読み込みは高速化された。前掲"New Firefox HTTP cache now enabled on Nightly builds"でHonza Bambas氏が示しているところによれば、キャッシュされていないページを読み込んだときに表示完了までに要する時間が明らかに短縮され、キャッシュされたページを閲覧する場合も、読み込みが若干速くなっている。つまり、これまではディスクにデータを蓄える処理が全体の足を引っ張っていたが、cache v2ではそのボトルネックが解消されたわけだ。

f:id:Rockridge:20140915160404p:plain

ディスク使用量の上限をスマートに設定

cache v2におけるディスクキャッシュの上限は、デスクトップ版が350MB、Android版が200MBとなっている(Bug 987829)。旧HTTPキャッシュ(以下cache v1)におけるディスクキャッシュの上限は、Firefox 17以降、デスクトップ版が1GB、Android版が200MBだった(Bug 709297)ので、デスクトップ版の上限がかなり低下した。

キャッシュサイズは、ディスクの空き容量に応じて10MB単位で自動的に決定される。空き容量が100GBを超えているときは、直ちに上限値が設定されるが、そうでない場合は計算が行われる。25GBを超える領域の0.5%、7GBを超える25GB以下の領域の1%、500MBを超える7GB以下の領域の5%が順次加算されていき、さらにデスクトップ版では500MBまでの領域の40%が加算されて下限が50MBとなるが、Android版ではストレージのフットプリントを考慮し、500MBまでの領域の20%が加算されて下限が10MBとなる。

古いエントリを適切に消去

cache v2では、保存するデータがディスクキャッシュの上限に達した場合、不要なエントリから適切に排除されるようになっている(Bug 913808)。エントリの選別にはfrecencyと呼ばれるアルゴリズムが採用されており、最近保存されたエントリや、頻繁に再利用されるエントリは残る仕組みだ。

キャッシュエントリをすべて削除する際は、新しいディレクトリを作成してそちらをアクティブ化し、古いディレクトリを削除する(Bug 968106)。しかも、ディレクトリの削除処理は低い優先度で実行される(Bug 968101)ため、Firefox本体への負担が少ない。

cache v2に切り替わることで、cache v1のデータが残ってしまうが、これもFirefox側で自動的に削除してくれる(Bug 1014394Bug 1045886)。特にAndroid版では、古いデータが残っていると貴重なストレージ容量を圧迫することになるため、重要な機能といえよう。

旧APIは廃止

以上のようにcache v2はcache v1とは別物であり、これに伴ってAPI(HTTP Cache | MDN参照)も全面的に刷新されたため、Firefox 32では、互換性のないcache v1のAPI群は廃止された。

APIが廃止になるとキャッシュからファイルを拾うタイプのアドオンなどに影響が出そうだが、mayhemer's blog"Firefox HTTP cache v1 API disabled"のコメント欄を見ると、2014年6月の時点で、Mozilla Add-ons(AMO)に登録されているアドオンについてはソースコードの調査が行われたらしく、現在もメンテナンスがされているアドオンの中で、マイグレーションが必要なものは見当たらなかったという。