Ads by Google
新しい記事を書く事で広告が消せます。
みんなの動画サーチを実例としたPHPでの開発における負荷対策について 第8回 では負荷対策には必須の技術、出力キャッシュの利用についてです。
以前インデックスぺージで秒間あたりの表示数が4倍になったと書いたことがありますが、それは後で述べるAPC利用もさることながら、この出力キャッシュを利用する処理を追加したからです。それほどに効きます。負荷対策には必須の手法と言えます。
ではさっそくはじめます。
ではどれくらい効果があるのかをまずご覧ください。こちらはABを使って開発環境で出力キャッシュありの場合となしの場合を比較した結果です。
なお、利用したコマンドは「ab -n 100 -c 10 http://192.168.0.2/movie-search/www/」です。
インデックスページ
キャッシュあり Requests per second: 15.77 [#/sec] (mean) キャッシュなし Requests per second: 3.91 [#/sec] (mean)
ごらんのとおり、キャッシュなしの場合と比べ、約4倍のパフォーマンスとなっています。インデックスはその性格上大量の情報を載せるためデータベースへの接続が多く(4回)、他のページよりもキャッシュの効果が高い傾向があります。
他のページは約2倍のパフォーマンスとなっています。
タグページ
キャッシュあり Requests per second: 15.42 [#/sec] (mean) キャッシュなし Requests per second: 7.25 [#/sec] (mean)カテゴリページ
キャッシュあり Requests per second: 14.19 [#/sec] (mean) キャッシュなし Requests per second: 7.30 [#/sec] (mean)
動画表示ページ
キャッシュあり Requests per second: 14.45 [#/sec] (mean) キャッシュなし Requests per second: 8.22 [#/sec] (mean)
「処理結果を一度ファイルに保存しておく」これが出力キャッシュです。作成されたキャッシュがある場合はデータベースへの接続やレンダリングエンジンの利用がないため、はるかに高速かつ低負荷で表示することができます。高い負荷がかかるランキングシステムなどには必ずと言っていいほど使われているようです。(時間は5分おきであったり1時間おきであったりと千差万別です)
単純に表示が速くなるほかに、CPUやデータベースサーバへの負荷が削減できる点もメリットです。そのため、キャッシュを導入すると、他のキャッシュを利用していないページにも効果が表れます。
キャッシュが作られていない場合は重いのですが、これは仕方ないと割り切るか、バックグラウンドであらかじめキャッシュファイルを作成しておくと良いです。
ちなみにみんなの動画サーチでは割り切っています。キャッシュなしでも致命的に遅いわけではない、バックグラウンドでキャッシュファイルを作るにはページが多すぎる、という理由によります。
例によってみんなの動画サーチからコードをひっぱってきます。
これはトップページ表示画面のビジネスロジック部分を抜き出したものです。indexクラスがメインの処理、cache()とgetcacheAction()がキャッシュを利用するための関数です。
getcacheAction()をindex::execute()の外に出しているのは、indexクラス以外にtagクラスやcategoryクラスなどほかの処理でも同じことをやるためです。
//$Timeは1あたり12時間。キャッシュがないか時間切れならFalseが返る。
function cache($Filename,$Time=2){
if(file_exists($Filename)){
if(time() - filemtime($Filename) <= 60*30*24*$Time){
return file_get_contents($Filename);
}
}
return false;
}
function getcacheAction($Action , $Filename , $LifeTime){
$Html = cache( $Filename , $LifeTime);
if(empty($Html)){ //HTMLファイルが返ったことを確認してから処理を省く
$Action->main();
$Renderer = new Renderer_Smarty;
$Renderer->setConfig(DEFMODULE);
$Html = $Renderer->fetch($Action->tpl , $Action->Vals);
file_put_contents($Filename , $Html);
}
return $Html;
}
class index extends Action
{
function Execute ()
{
//
//ページングがあるからそれも考慮。
$start = isset($this->GPVal["start"]) ? $this->GPVal["start"] : 1 ;
$cachefile = BASE_DIR.'cache/index/'.stripslashes_deep($start).".cash";
exit(getcacheAction($this , $cachefile , 0.1));
}
function main()
{
//省略するが、ここにはデータベースへの接続などのビジネスロジックが存在する。
$this->sendVal("TagCloud" , $TagCloud); //レンダリングエンジンに渡したい変数を指定している
$this->designateTpl("index.tpl"); //利用するテンプレートを指定している
}
}
ポイントは空白ではないHTMLデータが返ったことを確認してから処理を省く仕様になっている点です。キャッシュファイルがあるかどうかだけで処理をはぶく仕様にすると、とてもタイミングが悪い場合に表示がされません。たとえばこういう処理です。
if(file_exists("index.cash")){ //ここでキャッシュ判定した後で
//こちらの処理に移る前に不運にしてキャッシュファイルの内容が消えてしまった場合
print file_get_contents("index.cash"); //ここでは空白が表示される(=期待した表示はされない)
return;
}
$Html = makeHtml();
file_put_contents("index.cash",$Html); //ここで内部的にいったん内容を消してから作成している。
print $Html;
file_get_contents()やfile_put_contents()はファイルロックを行なわないので、非常に運が悪いとキャッシュ読み込みとキャッシュ再作成のタイミングが重なり、"index.cash"の内容が消去された後で読み込んでしまう可能性があります。マルチタスクであるWEBは注意しないといけない点です。(こんな不運はめったにないのもたしかです――ですがだからこそ気がつきづらく困りやすいです)
二重ロックをかけるなどして排他処理をきっちかけつつ上記のような処理を行えばこのような不運は起こらないわけですが、いちいち二重ロックをかけるのはそれはそれで負荷がかかって嫌なので、今回は選択しませんでした。
WEB APIやRSSなど、外部からデータを取得する場合は、このキャッシュの利用は必須です。
表示を早くする、負荷を軽減するという目的もありますが、なによりリクエストを送る相手へ負荷をかけすぎないためです。迷惑ですし、また、なんらかのペナルティがかけられる可能性もあります。
たとえば、ある大規模サービスには過去このキャッシュが効いていないバグがあり、膨大なリクエストを送った結果としてWEB API提供元からアクセスを遮断される羽目になったことがあるそうです。(そのサービスの開発者ブログで報告されていました)
影響範囲は多大なものがありますから、必ずキャッシュを利用するようにします。
もちろんみんなの動画サーチでも外部データを利用する場合必ずキャッシュを利用しています。
とても効果が高いキャッシュですが、デメリットもあります。
3番目と4番目が特に問題で、この理由により更新頻度が高い画面や、また更新がリアルタイムで行われる画面にはキャッシュは向きません。みんなの動画サーチでは「最近見た動画」がこれにあたります。
また、利用頻度が低く、ライフタイムの時間当たり1回しか見られないような場合もキャッシュはあまり意味をなしません。
そのためキャッシュを使えば使うほどいいというわけではなく、どこで利用するかはよく検討しないといけません。(できれば設計時に)
みんなの動画サーチ 現在の人気動画