2009年9月24日木曜日

SSDによるコンテンツキャッシュ(ソフト編)

このエントリーをはてなブックマークに追加
ZFSにはSSDをキャッシュに使ってランダムリードの性能を稼ぐL2ARCという仕掛けがあります。L2ARCはOpenSolarisでは使えるのですが、Solaris 10ではまだ使えません。10u6 (10/08)で使えるようになると言われていたのが延期されて、今年の10u7 (5/09)で使えるだろうと思っていたのが、また延期されてしまいました。実は10u7に合わせてSSDを購入したのですが、当てが外れてしまいました。

仕方ないのでバージョン2.2から実用可能になったApacheのmod_disk_cacheを使おうかと思ったのですが、これがうまくありませんでした。

まず、リクエストが来るとファイルを全部キャッシュするまでレスポンスを返しません。4GBあるDVDのISOイメージをキャッシュするまでクライアントを待たせてしまう可能性があります。せっかちなクライアントはタイムアウトしてしまうかもしれません。

ディスクキャッシュを掃除するhtcachecleanも、レスポンスにExpireヘッダがないとファイルのmtimeを見て古いのから消してしまいます。人気のあるファイルをキャッシュしたいという我々の思惑とは程遠いです。

仕方ないので、ディスクキャッシュの仕掛けを自前で作ることにしました。この仕掛けは以下の三つの要素からなります。
  1. Apacheのログを元にキャッシュするファイルの一覧を更新して、一覧から消えたファイルをキャッシュから消すスクリプト
  2. 30分に一度ファイルの一覧に基づいて、ドキュメントルートの.cacheディレクトリにマウントされたSSDにファイルをコピーするrsync
  3. 各リクエストについて、先に.cacheの下を調べてファイルがあればターゲットを.cacheの下に書き換える書き換え規則
  4. RewriteCond /DocumentRoot/.cache/$1 -f
    RewriteRule ^/(.*)$ /DocumentRoot/.cache/$1
    
ミラーサーバ上のファイルはめったに上書きされることはないので更新の検査はしません。上書きされる可能性が高いファイルはキャッシュの対象から外しておきます。タイムスタンプだけのファイルも外れるように、小さすぎるファイルも外します。もし想定外のファイルが上書きされてもrsyncで30分以内に反映されます。実用上はこれで十分です。

ポイントはキャッシュするファイルを決めるアルゴリズムです。キャッシュの更新間隔は最短で30分ですが、単純にLRUで30分ごとにキャッシュするファイルの一覧を更新すると、新たにキャッシュされるファイルが多すぎてSSDへのコピーが30分で終わりません。

書き込み中はSSDの読み込み性能が低下しますし、コピー元のディスクアレイにも負荷がかかります。それに書き込みはSSDの寿命を縮めます。ずっとコピーが続く状態は避けなければなりません。しかし、アクセスの多いファイルはなるべく早くSSDに送り込む必要があります。

試行錯誤の結果、現在ではキャッシュするファイルの決定にファイルの転送量の指数移動平均を用いています。指数移動平均の時間間隔は2時間で平滑化係数は0.02です。つまり直近2時間の転送量は2%しか反映されません。かなり控えめですが、ほとんどのファイルは散発的にしかアクセスされないので、アクセスの集中したファイルはすぐにキャッシュの対象になります。

キャッシュするファイルの一覧の更新も2時間に一度です。2時間ごとに入れ替わるファイルの数はかなり少なめに抑えられていますが、DVDやCDのISOイメージが入るとファイルのコピーに20分以上掛かることもあります。間隔を縮めるとしても1時間がいいところでしょう。

キャッシュするファイルを決めるスクリプトは、以下のようなCustomLogディレクティブを使って、標準入力からログを読み込むプログラムとして実行しています。
CustomLog "| /opt/.../bin/icache.pl" "%>s %O %f"
スクリプトは標準入力からレスポンスのステータスとデータの転送量とファイル名を読み込んで、ステータスが200番台のものについてファイルごとに転送量の指数移動平均を計算します。

CustomLogから起動されるプログラムは、遅滞なくログを読み続けなければなりません。そうしないとhttpdがリクエストを受け付けなくなってしまいます。次に述べる2時間ごとの処理は少し時間が掛かるので、この間止まるのを避けるために、このスクリプトではログを読むスレッドと処理するスレッドを分けています。

このスクリプトは2時間ごとに、ファイルサイズの合計が指定された値に達するまで、指数移動平均の大きな順にキャッシュするファイルを選びます。キャッシュされているファイルのうち、このときに選ばれなかったものとオリジナルが存在しなくなったものをキャッシュから削除します。すべてのファイルの指数移動平均を保持し続ける必要はないので、このときにデータを1万個まで減らします。

現在のキャッシュの合計の設定は500GB (465GiB)です。キャッシュされるファイルの個数は2000~3000個と幅があります。これはDVDのISOイメージが出入りするせいです。160GBのSSDを4つ束ねているのでファイルシステムの容量は587GiBありますが、あえて120GiBほど空けてあります。これはアクセスの殺到が予想されるファイルを事前に手作業でキャッシュに送り込めるようにするためです。メモリが64GiBになったから、もうその必要はないかもしれませんけど。

このようなキャッシュ機構を用いて、13TiBのコンテンツのうち3.5%の465GiBをSSDにキャッシュしただけで、平均で90%もヒットするようになり、ディスクアレイの負担は大きく軽減されました。

3 件のコメント:

  1. /.cache/ 以下も Browserable になってたのか。全然気にしてなかったな。。

    返信削除
  2. そういえば、最初はお試しって感じで、SSD で組んだファイルシステムに人気のあるコンテンツ(Fedoraとか)を直接置いていましたね。

    で、こりゃ猛烈に性能がいいぞってことで、さらに効果的に使うためにキャッシュとして使おうと色々とトライ。結局、てきとうスクリプト & mod_rewrite ってのが現状では一番という感じか。。。

    ZFS の L2ARC はやくこないかなぁ。。。

    返信削除
  3. Fedoraはでかすぎて丸ごと移せなかったから、最初に置いたのはCentOS、openSUSE、Eclipseあたりだよ。

    SSDの性能の良さもあるけど、むしろディスクアレイの負荷が軽減したことによる、全体としての出力向上が大きかった。だから、このエントリの結論もそうなってる。今でもSSDは性能の4%くらいしか発揮していないし。

    キャッシュの利点は、SourceForge.netみたいに一部のファイルに人気が集中していて、それが変化する場合の負荷を、効率よくSSDに散らせることだと思う。

    正直Zinくんが書いたスクリプトはいまいちだった。ブロックしてたしね。でも、キャッシュ機構の大枠はよかったし、キャッシュの利点もよくわかったから、僕もスクリプトの改善に注力した。

    まあ今だから言えることだけど、SSDより先にメモリだったね。

    返信削除