昨今はiOS、Android等のスマホ向けネィティブアプリやフロントエンドJavaScript向けの「サーバ側」機能として、RESTfulなHTTPプロトコルベースの Web API を作る機会が多いかと思います。
その際は PHP や Perl、Python、Ruby など諸々の言語でAPIスクリプトを作成しますが、サーバに対するAPIリクエスト数が増えてくるとサーバの負荷も増え、今度は負荷対策に悩まされることになってきます。
WEBサーバとして Apache を利用している場合は、mod_cache モジュールを利用して簡単にAPIレスポンスをApacheレイヤーにキャッシュできますので、今回はその方法を紹介します!
要点
下記に長々と設定例を書きますが、簡単に要点を説明すると、
- Apacheの設定で、mod_cacheモジュールの有効化とモジュールの初期設定をする
- キャッシュさせたいAPIレスポンスのHTTPレスポンスヘッダに
Cache-Control: max-age=秒数
を付与する(秒数のところはサーバにキャッシュさせたい時間を指定)
これだけです!後は mod_cache が同一内容の2度目以降のAPIリクエストに対して有効期限のキャッシュありなしを判定し、キャッシュがあればスクリプトに処理が渡る前に、キャッシュの内容をクライアント端末へレスポンスしてくれます。
前提
キャッシュ対象にすべきAPI
APIレスポンスをキャッシュするということは、スクリプトに処理が渡らないということです。なので、どういうAPIをキャッシュできるかというと、次のようなAPIになるかと思います。
- サーバ側のDBデータを更新しない
- 複数のクライアント端末で同じGETリクエスト(URL、クエリ文字列)&同じレスポンス内容で構わない
つまり「マスター参照系のAPI」です。
※キャッシュされるのは Cache-Control: max-age=秒数
を付与したAPIだけなので、キャッシュしたらまずいAPIは何もしなければいいだけです。
私が動作確認した環境
- EC2: Amazon Linux AMI 2013.09.2 (64-bit)
- Apache/2.2.26
このApacheはこのエントリのように yum でインストールしたものです
Apacheの設定
mod_cache(mod_disk_cache)の有効化
もし有効になっていなかったらhttpd.conf
を編集します(私の環境ではApacheのインストール時点で初めから有効になっていました)。
LoadModule cache_module modules/mod_cache.so
LoadModule disk_cache_module modules/mod_disk_cache.so
mod_mem_cache(ファイルでなくメモリにキャッシュ)というのもあるのですが、どうやらApacheがworkerモードで動作してないと意味が無いようなので、今回は mod_disk_cache を利用しています。
mod_cache(mod_disk_cache)の設定
httpd.conf
を編集します。
※編集後にApacheを再起動して、設定内容をApacheへ反映するのを忘れないように。
CacheRoot /var/www/html/cache
CacheIgnoreCacheControl On
CacheIgnoreHeaders Set-Cookie
CacheEnable disk /api/
#CacheEnable disk /api/hoge.php
#CacheDisable /api2/
#CacheDisable /api2/hoge.php
#CacheIgnoreQueryString On
#CacheIgnoreURLSessionIdentifiers hoge
最低限、必要な記載は、
CacheRoot /var/www/html/cache
CacheEnable disk /api/
の2行だけです。CacheRoot
にはキャッシュファイルを保存するディレクトリを指定します(Apacheから書き込めるようchmod 777
しておく)。
CacheEnable
にはキャッシュ対象とするHTTPリクエストのURLを指定します。この例だとhttp://hoge.com/api/
配下のURLに対してキャッシュが有効になります(ただし前述のとおりHTTPレスポンスヘッダにCache-Control: max-age=秒数
を付与した場合のみ)。
例ではコメントアウトしていますが、どうやら前方一致のようなので、スクリプト名をダイレクトに指定することも出来ます。
一応、それ以外の設定を説明をすると、
CacheIgnoreCacheControl On
..クライアント端末から Cache-Control: no-cache
が送られてきた場合もキャッシュ対象にする。一応記載しておいた方がよさそう。
CacheIgnoreHeaders Set-Cookie
..キャッシュにcookieセットを含めない。確かにまずそうなのでこれも一応記載しておく。
CacheDisable /api2/
..CacheEnable
と逆に、キャッシュしないURLを指定。
CacheIgnoreQueryString On
..これはAPIによっては重要な設定。クエリ文字列を無視してURLだけでキャッシュありなしを判定する場合は On にする。
CacheIgnoreURLSessionIdentifiers hoge
..これもAPIによっては重要な設定。特定のクエリ文字列を無視する場合はここに記載する。
詳しくはこのエントリの最後に記載している参考URLの、Apache公式ドキュメントをご覧ください。
動作確認
PHPの場合は次のようなスクリプトを用意し、2回目のリクエストが瞬時に応答すればキャッシュは効いています。キャッシュファイルは httpd.conf
の CacheRoot
で指定したディレクトリに作成されます。
また60秒後にちゃんとキャッシュが無効になっていることも確認してみてください!
<?php
//重い処理を模するために10秒スリープ
sleep(10);
header('Content-Type: text/plain; charset=UTF-8');
header('Cache-Control: max-age=60');
echo "Hello World!";
?>
おまけ:.htaccessでキャッシュ制御をしてみる
スクリプト言語側で Cache-Control
ヘッダを吐いてもいいのですが、既存のプログラムをいじりたくないという場合は、次のように.htaccess
でもヘッダを制御できます。
mod_headerとmod_rewriteを利用しています。
RewriteEngine on
# Api for 60 seconds cache
RewriteCond %{REQUEST_URI} ^/api/hoge1\.php$ [OR]
RewriteCond %{REQUEST_URI} ^/api/hoge2\.php$ [OR]
RewriteCond %{REQUEST_URI} ^/api/hoge3\.php$
RewriteCond %{REQUEST_METHOD} ^GET$
RewriteRule .* - [E=X_USE_CACHE_60SEC:]
# Api for 120 seconds cache
RewriteCond %{REQUEST_URI} ^/api/hoge4\.php$ [OR]
RewriteCond %{REQUEST_URI} ^/api/hoge5\.php$ [OR]
RewriteCond %{REQUEST_URI} ^/api/hoge6\.php$
RewriteCond %{REQUEST_METHOD} ^GET$
RewriteRule .* - [E=X_USE_CACHE_120SEC:]
# Default setting
Header set Cache-Control "max-age=0"
Header unset Last-Modified
Header unset Etag
Header unset Expires
Header unset Vary
# Cache setting
Header set Cache-Control "max-age=60" env=X_USE_CACHE_60SEC
Header set Cache-Control "max-age=120" env=X_USE_CACHE_120SEC
この例の場合は、hoge1.php``hoge2.php``hoge3.php
は60秒キャッシュされ、hoge4.php``hoge5.php``hoge6.php
は120秒キャッシュされます。
ここに記載しないphpファイルは、max-age=0
が付与されるのでキャッシュされません(max-ageを指定しなければキャッシュされないようですが念のため 0 を指定)。
Header unset
で4つのヘッダを一応削除しています(残ってるとなんとなく気持ち悪いので)。
ただ Vary だけは、サーバ環境によってはきちんと削除しておかないといけません。例えば Vary の値が User-Agent となっていると、リクエストパラメータ(URL、クエリ文字列)かつユーザエージェントが完全に一致しないと、キャッシュが効かなくなります。いまの時代、ユーザエージェントは膨大な種類があるので..
おわりに
例をapiとしていますが、動的なWEB系スクリプト処理であれば全て同じようにキャッシュできます!また1時間(max-age=3600)、1日(max-age=86400)という長いキャッシュ時間を設定することも可能です。
今回は同一WEBサーバ内の動的コンテンツをキャッシュする設定を説明しましたが、応用して次のようなキャッシュの使い方も可能だと思われます。
- リバースプロキシサーバとしてApacheを立て、コンテンツ生成サーバで作成される動的コンテンツをプロキシサーバにキャッシュする。
- 外部サイトで提供されるのAPIが重いor不安定な場合に、ローカルサーバに外部サイトのAPIレスポンスをキャッシュする
これらは近いうちに検証して、また別のエントリで説明します。