こんにちは、アリタノリヒロです。
前回の記事(PHPのcurl_multi機能)は、並列に通信することで高速化しようという話でした。今回は、通信そのものの数を減らすアプローチを考えてみましょう。
■モデルケース
前回と同じように、いくつかのAPIから情報取得してマッシュアップするページがあったとします。
■問題点
1台のサーバが同時に通信できる量にも限界はあるので、やみくもにAPIへの通信量を増やすとサーバの負荷が高まり、いずれ悲鳴をあげます。1アクセスあたり裏側で3本の通信が発生する仕組みの場合、もし同時に100人からアクセスされたら、100本分ではなく計400本もの通信が発生してしまう(表100本+裏300本)、と考えなければなりません。
本当にこんなにたくさん通信をする必要があるのでしょうか?できるだけ減らす事はできないでしょうか。
■改善のポイント
情報の鮮度に注目してみることにしましょう。1分後、10分後、1時間後・・・、それは見るたびに違う内容でしょうか?
頻度 |
APIの情報の例 | 特徴 |
---|---|---|
高 | ・何かの検索結果 ・株価 |
どんなキーワードで検索するかによって、ほぼ毎回APIからのレスポンス内容が変わるもの。 または株価のように秒単位で変化する等、リアルタイム性が高いもの。 |
中 | ・ニュース記事 ・電車遅延情報 |
数分おきに確認すれば十分。 |
低 |
・天気予報 ・ランキング ・RSS |
そもそものリアルタイム性が低く、数時間おきや数日おきにしか内容が変わらない。 |
こうして見ると、仮に5分程度ライムラグがあってもさほど影響が無いものが多い、つまり毎度APIに問い合わせるのが無駄とも言えないでしょうか。(毎度通信すべきはなのは、上の表では「高」の部分のみ)。
そこで、APIから取ってきたデータ(XML)を少しの時間だけとっておくのはどうでしょう?(リアルタイム性が高いものや検索結果については毎度通信し、それ以外のものはキープしておき再利用)アクセスしてきたAさん、Bさん、Cさん・・・誰が見ても同じ内容ならなおさらみんなでシェアできれば、通信の数もそれにかかる時間も減るはずです。
このように一定時間データを溜めて再利用するシステムや行為を、キャッシュ(cache ※1)といいます。 どんな言語でも、こんな流れのロジックが書ければ実現できるでしょう。
if ( とっておいたXMLが賞味期限切れ ) {
捨てる;
}
if ( とっておいたXMLがある ) {
$xml = そのXMLを再利用;
} else {
$xml = 新たにAPIからXMLを取得;
このXMLを保存するなどして手元にとっておく;
}
※1: キャッシュというとSquidやmemcachedなど複雑なシステムをイメージされるかもしれませんが、今回の事例はPHPだけで実装した簡易なものです。データの一時保存先はメモリでもファイルでも、SQLiteでも何でもよいと思います。通信するよりは早いですから。
■実装例1:APCの利用
PHPでのサンプルコードを示します。(コードを簡単にするため、APIの代わりにRSSで示します)
APCはメモリを利用してPHPのコードの実行を高速化できる仕組みですが、実は任意の値を有効期限付きでキープしてくれる apc_store()、apc_fetch()、といった道具が用意されいるので、これを利用してみましょう。みなさんがこの関数群を使える環境なら、これが一番ラクチンではないでしょうか。(※2)
$url = "http://dailynews.yahoo.co.jp/fc/entertainment/rss.xml";
$xml = apc_fetch($url); // URLをキーとし、キャッシュから保存されてるXMLを取得 (※3)
if ( $xml === false ) { // 無かった場合は、
$xml = file_get_contents($url); // 新たにAPIからXMLを取得(※4)
apc_store($url, $xml, 60); // そのXMLを、URLをキーとして保存(ここでは賞味期限60秒)
}
var_dump($xml); // 取得結果
※2:Yahoo! JAPAN内部でも独自のキャッシュライブラリがありましたが、これもまたオープンソースの機能をそのまま使った方が楽ということで、最近はAPCを利用するケースが増えてきました。
※3:賞味期限が切れてない場合だけ返してくれるので、60秒以内の情報ならキャッシュを再利用、それ以上古かったら最新の情報をダウンロードします。便利ですね。
※4:前回も触れましたが、Yahoo! JAPAN内部ではfile_get_contents()は使っていません。前回の記事のcurl_multiのロジックにでも組み入れて使ってみるとよいでしょう。
■実装例2:PEAR::Cache_Liteの利用
APCを使えないなら、変わりにこんなPEARのライブラリを使ってみてもよいでしょう。ファイルとして保存しれくれるようですが、処理の流れはほぼ同じです。
require_once('Cache/Lite.php');
$url = "http://dailynews.yahoo.co.jp/fc/entertainment/rss.xml";
$cache = new Cache_Lite( array('cacheDir'=>'/tmp/', 'lifeTime' =>60) ); // 賞味期限60秒としている
$xml = $cache->get($key); // URLをキーとして、保存されたキャッシュを取得してみる。
if ( $xml === false ) { // 無かった場合は、
$xml = file_get_contents($url); // 新たにAPIからXMLを取得
$cache->save($xml, $key); // そのXMLを、URLをキーとして保存
}
var_dump($xml); // 取得結果
■注意点
最後に、気をつけて頂きたいポイントです。
- ユーザ固有のデータを扱う所は、キャッシュ機能を入れるべきかよく検討しましょう。もしAさんの個人情報をキャッシュし、それをBさんにシェアしてしまったら大問題です。
- 投稿/編集の前後の流れも注意したいところです。 例えばキャッシュのせいで「Blogの誤字を修正したのに、何度保存ボタンを押しても(画面上)修正が反映されない」ということが起こると、お客さんに不信感を与えてしまいます。お客さんがすぐに確認したいような場面では、キャッシュを使わない、あるいはキャッシュをクリアするような処理を入れるべきでしょう。
- キャッシュは、1分や5分でも十分でしょう。1分間有効なキャッシュ入れれば、元の通信量が100本/分だろうが1000本/分だろうが、1本/分に減ります。さらに通信を減らす為に1時間もキャッシュ保持する必要性は感じません。(むしろあまり長いと、お客さんに届ける情報のタイムラグが広がる)
- キャッシュのお掃除も気をつけましょう。ファイルに保存しておく場合、ひたすら溜め込んでしまうとディスクを消費するので、賞味期限が切れたものは削除するなどしましょう。
キャッシュの考え方はWebサービスに限らずデスクトップアプリやiPhoneアプリ等でも有効と思いますので、是非検討してみてください。
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました