.htaccess を利用して Data API のキャッシュの仕組みをつくる

  • 投稿日:
  • by
  • カテゴリ:

この記事について

この記事は Movable Type Advent Calendar 2013 の3日目の記事です。

概要

Data API をキャッシュする仕組みとしては既に PHP を使ったものなどがあるようですが、この記事では .htaccess を使い、以下のような方針でキャッシュする方法を紹介します。

  • クライアント側の設定 (mt-data-api.cgi へのエンドポイント) を変更をせずにキャッシュを利用できる
  • 共用サーバーでも利用できる
  • プログラムのレイヤまで降りずに Apache から直接データを返すことができる

サンプルコード

.htaccess とプラグインのサンプルコードが以下の URL から参照できます。

DataAPIPublicCache

ここからはサンプルコードを参照しながら説明をしていきます。

キャッシュの戦略

キャッシュをする際にまず考慮しなくてはならないのが、以下の点になります。

  • MT 側でデータが更新されたらキャッシュがクリアされること
  • 閲覧に認証が必要なデータをキャッシュしてしまい、未認証のユーザーに見られてしまうことがないこと

この記事で紹介する方法では、それぞれ以下の方針で対応をしています。

  • データが更新された場合にはプラグインを使ってキャッシュをクリアする
  • 閲覧に認証が必要なデータはキャッシュしない

.htaccess の戦略

静的なファイルを mt-static/support/data-api-public-cache/ 以下に書き出し、mt-data-api.cgi/v1/... へのアクセスを .htaccess を使ってマッピングして、ファイルが作成済みの場合にのみそれを返すようにします。

キャッシュファイルへのマッピングは一般的にはURLとパラメータキーにして、それを MD5 などでハッシュ化することが多いと思いますが、.htaccess ではそういった操作はできないので基本的にはパラメータをそのままファイル名にしています。

ただパラメータを直接ファイル名にすると任意の名前でファイルを作成できてしまい「/..」などで問題が発生しそうだったので、キャッシュする際にパラメータの文字列をホワイトリストで制限した上で、「/..」と「%」をエンコードする形をとっています。

ファイル名の制限について

ファイル名に関して一つのポイントとして、OS やファイルシステムにより文字数に制限がある場合があります。一般の UNIX 的な環境だと、個々のディレクトリやファイルの文字数の制限は 255 文字程度のようなので、名前が制限内に収まるように適当にディレクトリに分割する必要があります。(トータルの長さは 255 文字を超えても問題ないので)

またディレクトリを含めたトータルの長さについても、1023 文字などの制限がある場合があるようです。手元の Linux 環境では特にこの点は工夫をしなくても Apache 側、Perl 側、共に1023 文字より長くても大丈夫でしたが、Mac 環境では Apache 側でファイルを見つけられなくなりました。この記事ではこの問題に関しては解決策を用意できていないので、この問題にあたった場合には制限事項となっています。

.htaccess の内容

.htaccess の内容は以下のようなものになります。

RewriteEngine On
RewriteBase /mt/

RewriteCond %{HTTP:X-MT-Authorization} =""
RewriteCond %{REQUEST_METHOD} =GET
RewriteCond %{QUERY_STRING} ^[a-zA-Z0-9!""\$\%\&\'\(\)\=\~\^\|\\\{\[\`\@\}\]\*\:\+\;\_\?\/\>\.\<\,-]*$
RewriteRule data-api.cgi/(v.*) mt-static/support/data-api-public-cache/$1/%{QUERY_STRING}c.js [N,E=HTTP_X_DATA_API_PUBLIC_CACHE_TRY:1]

RewriteRule (mt-static/support/data-api-public-cache/.*?)%(.*c.js)/v\d+ "$1 $2" [N]
RewriteRule (mt-static/support/data-api-public-cache/.*?)/\.\.(.*c.js)/v\d+ $1/_..@$2 [N]
RewriteRule (mt-static/support/data-api-public-cache/.*?)(/[^/]{250})([^/].*c.js)/v\d+ $1$2/$3 [N]

RewriteCond %{SCRIPT_FILENAME} !-f
RewriteCond %{ENV:HTTP_X_DATA_API_PUBLIC_CACHE_TRY} =1
RewriteRule mt-static/support/data-api-public-cache/(.*c.js)/v\d+ mt-data-api.cgi [L,E=HTTP_X_DATA_API_PUBLIC_CACHE_FILENAME:$1]

RewriteCond %{SCRIPT_FILENAME} !-f
RewriteCond %{ENV:HTTP_X_DATA_API_PUBLIC_CACHE_TRY} !=1
RewriteRule mt-static/support/data-api-public-cache/ - [R=403,L]

静的ファイルの書き出し

基本的には単純にプラグインで Data API の出力をフックし、未認証の場合にファイルに書き出すだけです。

書き出すファイル名の生成は Perl 側で実装すると .htaccess とルーチンが重複するので、.htaccess から渡すようにします。ただその際、リクエストパラメータとして渡すと外部からファイル名を指定できてしまう可能性があり危険なので、環境変数として渡すようにしています。(そのため PSGI 環境では動作しません、CGI 環境でのみ動作します)

環境変数として渡す場合に一つ注意が必要なのは、CGI が SuExec 環境で実行されていると渡すことのできる名前に制限があるという点です。任意の値を渡す場合には HTTP_X_ で始まるようにする必要があります。

性能の比較

パラメータを MD5 でハッシュ化して readfile するような簡単な PHP を追加して、「この記事の .htaccess を使った方法」「PHP」「CGI」「PSGI」で、いくつかのパラメータのパターンでリクエストにかかる時間を比較してみました。

fields=id,title,excerptfields=...&search=あfields=...&search=あ...(5文字)fields=...&search=あ...(10文字)
.htaccess 0.394 0.424 0.474 0.565
PHP 0.46 0.469 0.469 0.464
CGI 408.793 422.225 421.653 417.161
PSGI 57.613 64.643 62.714 63.373
  • "ab -c 1" で計測した "Time per request" の値です
  • .htaccess と PHP に関してはキャッシュは作成済みの状態での値です
  • 開発環境の MacBookPro で計測した簡易的なものなので、絶対値にはあまり意味はありません

.htaccess と PHP の比較

.htaccess と PHP の結果をグラフにしたものが以下の図になります。

htaccess-php.png

PHP はパラメータの長さによらずほぼ一定の速度ですが、.htaccess の方は長くなるに従って (特に%が増えることによって) 内部でのリダイレクトの回数が増えるために急激に遅くなっています。

パラメータが長いことを前提とした場合には、以下の様な書き方をすることである程度速度の低下を抑えることもできますが、.htaccess の記述が増える分パラメータが短い場合の性能が悪化してしまいます。

RewriteRule (mt-static/support/data-api-public-cache/.*?)%(.*?)%(.*?)%(.*?)%(.*?)%(.*?)%(.*?)%(.*?)%(.*c.js)/v\d+ "$1 $2 $3 $4 $5 $6 $7 $8 $9" [N]
RewriteRule (mt-static/support/data-api-public-cache/.*?)%(.*?)%(.*?)%(.*?)%(.*c.js)/v\d+ "$1 $2 $3 $4 $5" [N]
RewriteRule (mt-static/support/data-api-public-cache/.*?)%(.*?)%(.*c.js)/v\d+ "$1 $2 $3" [N]
RewriteRule (mt-static/support/data-api-public-cache/.*?)%(.*c.js)/v\d+ "$1 $2" [N]

この記事の方法を使う限り、パラメータが長くなるにつれて性能が大きく悪化するのは避けられなそうです。

参考URL

次の記事について

次の記事(4日目)を書く人がまだ決まっていないようです!どなたかよろしくお願いいたします。