アプリケーションを加速する方法の1つは将来のリクエストのために生成されたHTMLコード、もしくは全ページのチャンク(塊)を保存することです。このテクニックはキャッシュ(caching)という名前で知られ、サーバーサイドとクライアントサイドで管理できます。
symfonyは柔軟なサーバーキャッシュシステムを提供します。キャッシュによって、YAMLファイルに基づいたとても直感的なセットアップを通して、全レスポンス、アクションの結果、部分テンプレートもしくはテンプレートフラグメントをファイルに保存できるようにします。基本的なデータが変化するとき、コマンドラインもしくは、特別なアクションメソッドによってキャッシュの選択した部分を簡単にクリアできます。symfonyもHTTP 1.1のヘッダーを通してクライアントサイドのキャッシュをコントロールする簡単な方法を提供します。この章ではこれらすべての題目を扱い、キャッシュがアプリケーションにもたらす改善をモニタリングするいくつかのティップスを提示します。
HTMLのキャッシュの原則はシンプルです: ユーザーに送信されるHTMLのコードの一部もしくはすべてが似たようなリクエストに対して再利用できます。このHTMLのコードは特別な場所(symfonyのcache/
フォルダー)に保存されます。ここでフロントコントローラーはアクションを実行するまえにこのコードを探します。キャッシュされたバーションが見つかる場合、アクションを実行しないでこのコードが送られるので、処理速度が大いに加速します。キャッシュされたバージョンが見つからない場合、アクションが実行され、将来のリクエストのために結果(ビュー)がキャッシュ用のフォルダーに保存されます。
すべてのページが動的な情報を含むので、デフォルトではHTMLのキャッシュは無効です。パフォーマンスを改善するために有効にするかどうかはサイトの管理者次第です。
symfonyは3つの異なるタイプのHTMLキャッシュを扱います:
最初の2つのタイプはYAMLの設定ファイルで扱われます。テンプレートフラグメントのキャッシュはテンプレートのヘルパー関数の呼び出しによって管理されます。
プロジェクトのそれぞれのアプリケーションのために、settings.yml
ファイルのキャッシュ設定において、HTMLキャッシュメカニズムは環境ごとに有効、もしくは無効(デフォルト)にできます。リスト12-1はキャッシュの有効のお手本を示しています。
リスト12-1 - キャッシュを有効にする(myapp/config/settings.yml
)
dev:
.settings:
cache: on
静的な情報(データベースに依存しないもしくはセッションに依存するデータ)を表示するアクション、もしくは修正なしでデータベースから情報を読みとるアクション(典型的なものはGETリクエスト)はしばしばキャッシュのために理想的です。図12-1はこの場合においてページの要素がキャッシュされることを示しています: アクションの結果(そのテンプレート)もしくはレイアウトとのアクションの結果です。
図12-1 - アクションをキャッシュする
たとえば、Webサイトのすべてのユーザーのリストを返すuser/list
アクションを考えてください。ユーザーが修正、追加、もしくは除去(そしてこのことが"キャッシュから項目を除去する"のセクションで検討されます)されないかぎり、このリストはつねに同じ情報を示しますので、キャッシュのためのよい候補です。
キャッシュの有効と設定、アクションによるアクションはモジュールのconfig/
ディレクトリに設置されたcache.yml
ファイルのなかで定義されます。例としてリスト12-2をご覧ください。
リスト12-2 - アクションのためのキャッシュを有効にする(myapp/modules/user/config/cache.yml
)
list:
enabled: on
with_layout: false # デフォルト値
lifetime: 86400 # デフォルト値
この設定は、キャッシュがlist
アクションのためにオンになっていること、およびレイアウトがアクション(デフォルトのふるまい)によってキャッシュされないことを保証します。このことは、キャッシュバージョンのアクションが存在する場合でも、(部分テンプレートとコンポーネントと一緒に)レイアウトはまだ実行されることを意味します。with_layout
設定がtrue
に設定された場合、レイアウトはアクションによってキャッシュされ、再び実行されません。
キャッシュの設定をテストするには、ブラウザーから開発環境のアクションを呼び出します。
http://myapp.example.com/myapp_dev.php/user/list
ページ内のアクションの領域周辺の境界に注目してください。最初に、領域は青色のヘッダーで、ページがキャッシュから由来するものではないことを示します。ページをリフレッシュすると、アクションの領域が黄色のヘッダーを持ち、キャッシュから由来するものであることを表します(レスポンスの時間単位で目立って加速されます)。後の章でキャッシュのテストとモニタリングを行う方法について詳しく学ぶことになります。
NOTE スロットはテンプレートの一部で、アクションをキャッシュすることはこのアクションのテンプレートのなかで定義されたスロットの値を保存することにもなります。ですのでキャッシュはスロットに対してネイティブに機能します。
キャッシュシステムは引数を持つページに対しても機能します。user
モジュールが、たとえば、ユーザーの詳細を表示するためにid
引数を必要とするshow
アクションを持つとします。リスト12-3で示されるように、このアクションに対してキャッシュを有効にするにはcache.yml
ファイルを修正します。
cache.yml
を編成するには、リスト12-3でも示されるように、all:
キーの下にあるモジュールのすべてのアクションのための設定を再分類できます。
リスト12-3 - cache.yml
の全内容の例(myapp/modules/user/config/cache.yml
)
list:
enabled: on
show:
enabled: on
all:
with_layout: false # デフォルト値
lifetime: 86400 # デフォルト値
user/show
アクションを異なるid
引数で呼び出すたびにキャッシュ内で新しいレコードが作られます。ですので、このためのキャッシュ:
http://myapp.example.com/user/show/id/12
はつぎのキャッシュとは異なります:
http://myapp.example.com/user/show/id/25
CAUTION POSTメソッドもしくはGETパラメーターによって呼び出されたアクションはキャッシュされません。
with_layout
設定は少し説明が必要です。この設定はどの種類のデータがキャッシュに保存されるのか実際に決定します。レイアウトなしのキャッシュに対しては、テンプレートの実行結果とアクション変数がキャッシュに保存されます。レイアウトありのキャッシュに対しては、レスポンスオブジェクト全体が保存されます。レイアウトありのキャッシュはレイアウトなしのキャッシュよりもずっと速いことを意味します。
機能的に余裕があれば(つまりレイアウトがセッションデータに依存しない場合)、レイアウトありのキャッシュを選ぶべきです。不幸なことに、レイアウトは動的な要素(たとえば、接続したユーザーの名前)を含むことが多いので、レイアウトなしのアクションのキャッシュはもっとも共通した設定です。しかしながら、RSSフィード、ポップアップとCookieに依存しないページはレイアウトありでキャッシュできます。
7章では、include_partial()
ヘルパーを利用して、いくつかのテンプレートにまたがるコードのフラグメントを再利用する方法を説明しました。部分テンプレートはアクションとしてキャッシュすることが簡単で、図12-2で示されるように、キャッシュの有効方法は同じルールに従います。
図12-2 - 部分テンプレート、コンポーネント、コンポーネントスロットをキャッシュする
たとえば、リスト12-4はuser
モジュールに設置された_my_partial.php
上でキャッシュを有効にするためにcache.yml
ファイルを編集する方法を示しています。この場合においてwith_layout
設定は意味を成さないことを覚えておいてください。
リスト12-4 - 部分テンプレートのキャッシュ設定(myapp/modukes/user/config/cache.yml
)
_my_partial:
enabled: on
list:
enabled: on
...
この部分テンプレートを利用するすべてのテンプレートは実際には部分テンプレートのPHPコードを実行しませんが、代わりにキャッシュバーションを使います。
[php]
<?php include_partial('user/my_partial') ?>
アクションに関しては、部分テンプレートの結果がパラメーターに依存するときに部分テンプレートキャッシュも該当します。キャッシュシステムはパラメーターの異なる値と同じ数のバージョンのテンプレートを保存します。
[php]
<?php include_partial('user/my_other_partial', array('foo' => 'bar')) ?>
TIP アクションのキャッシュは部分テンプレートのキャッシュよりもはるかに強力です。アクションがキャッシュされたとき、テンプレートの実行でさえも行われないからです; テンプレートが部分テンプレートの呼び出しを含む場合、これらの呼び出しは実行されません。それゆえ、部分テンプレートのキャッシュは、アクションを呼び出す際もしくはレイアウトに含まれる部分テンプレートに対してアクションのキャッシュを使わない場合のみ便利です。
7章を少し思い出してみましょう: コンポーネントは部分テンプレートのトップに設置される軽量のアクションで、コンポーネントスロットはアクションを呼び出すことにしたがって変化するアクションのためのコンポーネントです。これら2つのインクルージョンのタイプは部分テンプレートとよく似ており、同じ方法でキャッシュをサポートします。たとえば、現在の日時を表示するために、グローバルレイアウトがinclude_component('general/day')
でday
と呼ばれているコンポーネントをインクルードする場合、このコンポーネント上のキャッシュを有効にするにはgeneral
モジュールのcache.yml
ファイルをつぎのように設定します:
_day:
enabled: on
コンポーネントもしくは部分テンプレートをキャッシュするとき、呼び出しているすべてのテンプレートに対して単独のキャッシュバージョンか、それぞれのテンプレートに対するバージョンを保存するか決めなければなりません。デフォルトでは、コンポーネントはそれを呼び出すテンプレートに対して独立して保存されます。しかしながら文脈依存のコンポーネント、たとえばそれぞれのアクションで異なるサイドバーを表示するコンポーネント、はテンプレートがそれを呼び出す回数と同じ回数だけ保存されます。つぎのようにcontextual
パラメーターをtrue
に設定することを前提にする場合、キャッシュシステムはこの事例を扱うことができます:
_day:
contextual: true
enabled: on
NOTE アプリケーションの
cache.yml
のなかでキャッシュの設定を宣言することを前提にする場合、グローバルコンポーネント(アプリケーションのtemplates
ディレクトリに設置される)をキャッシュできます。
アクションのキャッシュはアクションの部分集合のみに適用されます。ほかのアクションに対して、データを更新する、もしくはテンプレートでセッションに依存する情報を表示するアクションなど、は異なる方法ですがまだキャッシュの改善の余地はあります。symfonyは3つのキャッシュタイプを提供します。これらはテンプレートのフラグメント専用でテンプレート内部で直接有効にできます。このモードにおいて、図12-3で示されるように、アクションはつねに実行され、テンプレートは実行されたフラグメントとキャッシュのフラグメントに分割されます。
図12-3 テンプレートのフラグメントをキャッシュする
たとえば、最後にアクセスしたユーザーのリンクを表示するユーザーのリストがある場合、この情報は動的です。cache()
ヘルパーはキャッシュに保存されたテンプレートの部分を定義します。リスト12-5で構文の詳細をご覧ください。
リスト12-5 - cache()
ヘルパーを使う(myapp/modules/user/templates/listSuccess.php
)
[php]
<!-- Code executed each time -->
<?php echo link_to('last accessed user', 'user/show?id='.$last_accessed_user_id) ?>
<!-- Cached code -->
<?php if (!cache('users')): ?>
<?php foreach ($users as $user): ?>
<?php echo $user->getName() ?>
<?php endforeach; ?>
<?php cache_save() ?>
<?php endif; ?>
つぎのように動作します:
<?php if (!cache($unique_fragment_name)): ?>
と<?php endif; ?>
の行の間のコードを置き換えるために使われます。このような行が含まれないコードはつねに処理され、キャッシュされません。
CAUTION アクション(この例では
list
)はキャッシュを有効にしてはなりません。これはテンプレート全体の実行を回避し、フラグメントのキャッシュの宣言を無視するからです。
テンプレートフラグメントのキャッシュの使用による加速はアクションのキャッシュによるものよりは重要ではありません。アクションはつねに実行され、テンプレートは部分的に処理され、レイアウトはつねにデコレーションのために使われるからです。
追加のフラグメントを同じテンプレートのなかで宣言できます; しかしながら、それらに対して個別にユニークな名前をつけることで、symfonyのキャッシュシステムはこれらをあとで見つけることができます。
アクションとコンポーネントに関しては、キャッシュされたフラグメントはcache()
ヘルパーへの呼び出しの2番目の引数として秒単位の期限を持ちます。
[php]
<?php if (!cache('users', 43200)): ?>
パラメーターがヘルパーに渡されない場合、デフォルトのキャッシュの期限(86400秒もしくは1日)が使われます。
TIP アクションをキャッシュできるようにする別の方法はアクションのルーティングパターンに変化する変数を挿入することです。たとえば、ホームページが接続ユーザーの名前を表示する場合、URLがユーザーのニックネームを含まないかぎり、キャッシュできません。ほかの例は国際化アプリケーションに関するものです: いくつかの翻訳を持つページの上でキャッシュを有効にしたい場合、言語コードを何とかしてURLパターンに含めなければなりません。このトリックはキャッシュにおけるページ数を増やしますが、インタラクティブなアプリケーションを大きく加速するために大いに役立ちます。
cache.yml
ファイルはキャッシュの設定を定義する1つの方法ですが、不変であるが故に不便です。しかしながら、symfonyにおいていつものとおり、YAMLよりも無地のPHPを利用できるので、キャッシュを動的に設定できます。
キャッシュの設定を動的に変更したいのはなぜでしょうか?よい例は認証ユーザーと匿名ユーザーのページの内容は異なるが、URLは同じであるページです。記事に対して評価システムを持つarticle/show
ページを想像してください。評価機能は匿名ユーザーに対して無効です。これらのユーザーに対しては、リンクはログインフォームの表示を行います。このバージョンのページはキャッシュできます。一方で、認証ユーザーに対しては、評価リンクをクリックすることでPOSTリクエストと新しい評価が作成されます。今回、symfonyが動的にページをビルドするにはページに対してキャッシュを無効にしなければなりません。
動的なキャッシュの設定を定義する正しい場所はsfCacheFilter
のまえに実行されるフィルターです。本当に、Webデバッグツールバーとセキュリティ機能と同じように、キャッシュはsymfonyのフィルターです。ユーザーが認証されていない場合だけarticle/show
ページに対してキャッシュを有効にするには、リスト12-6で示されるように、conditionalCacheFilter
をアプリケーションのlib/
ディレクトリのなかに作成します。
リスト12-6 - PHPを使用してキャッシュを設定する(myapp/lib/conditinalCacheFilter.class.php
)
[php]
class conditionalCacheFilter extends sfFilter
{
public function execute($filterChain)
{
$context = $this->getContext();
if (!$context->getUser()->isAuthenticated())
{
foreach ($this->getParameter('pages') as $page)
{
$context->getViewCacheManager()->addCache($page['module'], $page['action'], array('lifeTime' => 86400));
}
}
// つぎのフィルターを実行する
$filterChain->execute();
}
}
リスト12-7で示されるように、sfCacheFilter
のまえにこのフィルターをfilters.yml
ファイルに登録しなければなりません。
リスト12-7 - カスタムフィルターを登録する(myapp/config/filters.yml
)
...
security: ~
conditionalCache:
class: conditionalCacheFilter
param:
pages:
- { module: article, action: show }
cache: ~
...
キャッシュをクリアすれば(新しいフィルタークラスをオートロードするため)、条件つきのキャッシュの準備ができます。これは、認証されていないユーザーに対してだけ、ページパラメーターで定義されたページのキャッシュを有効にします。
sfViewCacheManager
オブジェクトのaddCache()
メソッドはモジュールの名前、アクションの名前、そしてcache.yml
ファイルのなかで定義するパラメーターと同じ内容を持つ連想配列を必要とします。たとえば、article/show
をレイアウトありで3600秒の期限の条件でキャッシュしなければならないことを定義したい場合、つぎのように書きます:
[php]
$context->getViewCacheManager()->addCache('article', 'show', array(
'withLayout' => true,
'lifeTime' => 3600,
));
SIDEBAR 代替のキャッシュストレージ
デフォルトでは、symfonyのキャッシュシステムはWebサーバーのハードディスク上のファイルにデータを保存します。メモリ(たとえばmemcached)経由)もしくはデータベース(いくつかのサーバー間でキャッシュを共有したいもしくはキャッシュの除去を加速したい場合)にキャッシュを保存したい場合を考えます。このためには、symfonyのデフォルトのキャッシュストレージシステムを簡単に変更できます。symfonyのビューキャッシュマネージャーによって使われるキャッシュクラスは
factories.yml
に定義されるからです。デフォルトのビューキャッシュストレージファクトリは
sfFileCache
クラスです:view_cache: class: sfFileCache param: automaticCleaningFactor: 0 cacheDir: %SF_TEMPLATE_CACHE_DIR%
class
を独自のキャッシュストレージクラスもしくはsymfonyの代替クラス(たとえばsfSQLiteCache
)に置き換えることができます。param
キーの下に定義されたパラメーターは連想配列としてあなたのクラスのinitialize()
メソッドに渡されます。ビューのキャッシュストレージクラスはsfCache
抽象クラスで見つかるすべてのメソッドを実装しなければなりません。この主題について詳しい情報はAPIドキュメント(http://www.symfony-project.org/api/1_0/)を参照してください。
キャッシュされたページでさえいくつかのPHPコードの実行をともないます。このようなページのために、symfonyは設定をロードし、レスポンスをビルドするなどの動作を行います。しばらくの間ページが変更されないことが本当にわかっている場合、HTMLコードの結果を直接web/
フォルダーに設置することでsymfonyを完全に回避できます。ルーティングルールがサフィックスで終わらない、もしくは.html
で終わるパターンを指定するという条件の下で、Apacheのmod_rewrite
設定のおかげでこれは機能します。
シンプルなコマンドラインの呼び出しによって、ページ単位で、これを手作業で行うことができます:
> curl http://myapp.example.com/user/list.html > web/user/list.html
そのあとで、user/list
アクションがリクエストされるたびに、Apacheは対応するlist.html
ページを見つけsymfonyを完全に回避します。トレードオフはもはやsymfonyでページキャッシュをコントロールできないことですが(期限、自動削除など)、速度のゲインは目覚ましいです。
代わりの方法として、sfSuperCache
プラグインを利用できます。このプラグインはこの処理を自動化して、期限とキャッシュのクリアをサポートします。プラグインに関して詳しい情報は17章を参照してください。
SIDEBAR ほかのスピードアップ戦術
HTMLのキャッシュに加えて、symfonyは他に2つのキャッシュメカニズムを持ちます。これらは完全に自動化され、開発者に対して透過的なものになります。運用環境において、設定とテンプレートの翻訳は介入なしで
myproject/cache/config/
とmyproject/cache/i18n/
ディレクトリに保存されたファイルにキャッシュされます。PHPアクセレータ(eAccelerator、APC、XCacheなど)はオペレーションコードキャッシュモジュールとも呼ばれ、コンパイルされた状態のPHPスクリプトをキャッシュすることでパフォーマンスを増大させるので、コードの解析とコンパイルのオーバーヘッドは徹底的に削減されます。これは大量のコードを含むPropelのクラスに対してとりわけ効果的です。これらのアクセレータはsymfonyと互換性があり、簡単にアプリケーションの速度を3倍にします。これらは大規模な利用者を持つsymfonyアプリケーションに対して運用環境で推奨されます。
sfProcessCache
クラスによる、それぞれのリクエストに対して同じ処理を行うことを避けるために、PHPアクセレータで、永続的なデータを手動でメモリに保存できます。そしてCPUに負荷のかかる作業の結果をファイルに保存したい場合、おそらくsfFunctionCache
オブジェクトを使うことになります。これらのメカニズムに関して詳しい情報については18章を参照してください。
アプリケーションのスクリプトもしくはデータが変化する場合、キャッシュは期限切れの情報を含みます。矛盾とバグを避けるために、あなたのニーズに合わせて、多くの異なる方法でキャッシュの部分を削除できます。
symfonyのコマンドラインのclear-cache
タスクはキャッシュ(HTML、設定と国際化のキャッシュ)を削除します。リスト12-8で示されるように、キャッシュの部分集合だけを削除するためにこれを引数に渡すことができます。symfonyのプロジェクトのrootからのみ呼び出すことを覚えておいてください。
リスト12-8 - キャッシュをクリアする
// キャッシュ全体を消去する
> symfony clear-cache
// 短い構文
> symfony cc
// myappアプリケーションのキャッシュのみを削除する
> symfony clear-cache myapp
// myappアプリケーションのHTMLキャッシュだけを削除する
> symfony clear-cache myapp template
// myappアプリケーションのコンフィギュレーションキャッシュのみを削除する
> symfony clear-cache myapp config
データベースが更新されたとき、修正されたデータに関連するアクションのキャッシュをクリアしなければなりません。キャッシュ全体をクリアできましたが、モデルの変更に関係のない既存のすべてのキャッシュされたアクションに対しては無駄な作業です。これがsfViewCacheManager
オブジェクトのremove()
メソッドを適用する事例です。これは引数として内部URIを必要とし(link_to()
に提供する引数と同じ種類のもの)、関連するアクションのキャッシュを削除します。
たとえば、user
モジュールのupdate
アクションがUser
オブジェクトのカラムを修正することを想像してください。キャッシュバージョンのlist
アクションとshow
アクションをクリアすることが必要です。さもなければエラーのあるデータを含む古いバージョンが表示されます。これを処理するには、リスト12-9で示されるように、remove()
メソッドを使います。
リスト12-9 - 任意のアクションに対してキャッシュをクリアする(modules/user/actions/actions.class.php
)
[php]
public function executeUpdate()
{
// ユーザーを更新する
$user_id = $this->getRequestParameter('id');
$user = UserPeer::retrieveByPk($user_id);
$this->foward404Unless($user);
$user->setName($this->getRequestParameter('name'));
...
$user->save();
// このユーザーに関連するアクションに対するキャッシュをクリアする
$cacheManager = $this->getContext()->getViewCacheManager();
$cacheManager->remove('user/list');
$cacheManager->remove('user/show?id='.$user_id);
...
}
キャッシュされた部分テンプレート、コンポーネント、コンポーネントスロットを除去する作業は少々複雑です。任意のタイプのパラメーター(オブジェクトを含む)をこれらに渡すことができるので、そのあとでキャッシュバージョンを識別することが不可能だからです。説明がほかのテンプレートのコンポーネントに対して同じである、部分テンプレートに焦点を当てましょう。symfonyはキャッシュされた部分テンプレートを、特別なプレフィックス(sf_cache_partial
)、モジュールの名前、部分テンプレートの名前、に加えて呼び出すために使われるすべてのパラメーターのハッシュで識別します:
[php]
// 呼び出された部分テンプレート
<?php include_partial('user/my_partial', array('user' => $user) ?>
// はつぎのようなキャッシュに分類される
/sf_cache_partial/user/_my_partial/sf_cache_key/bf41dd9c84d59f3574a5da244626dcc8
理論上では、識別するために使われるパラメーターのハッシュの値を知っているのであれば、キャッシュされた部分テンプレートをremove()
メソッドで削除できます。しかし、これは現実的な方法ではありません。幸いにして、sf_cache_key
パラメーターをinclude_partial()
ヘルパー呼び出しに追加する場合、あなたがご存じの何かによってキャッシュのなかに存在する部分テンプレートを識別できます。リスト12-10で見ることができるように、単独のキャッシュされた部分テンプレートをクリアする作業、たとえば、修正されたUser
に基づいた部分テンプレートからキャッシュをクリーンアップする作業が簡単になります。
リスト12-10 - 部分テンプレートをキャッシュからクリアする
[php]
<?php include_partial('user/my_partial', array(
'user' => $user,
'sf_cache_key' => $user->getId()
) ?>
//つぎのようにキャッシュに分類される
/sf_cache_partial/user/_my_partial/sf_cache_key/12
// $cacheManager->remove('@sf_cache_partial?module=user&action=_my_partial&sf_cache_key='.$user->getId());でキャッシュの特定のユーザーに対する_my_partialをクリアする
キャッシュのなかに発生した部分テンプレートをすべてクリアするためにこのメソッドを使うことはできません。この章の後のほうにある"キャッシュを手動でクリアする"のセクションでこれらをクリアする方法を学ぶことになります。
テンプレートフラグメントをクリアするには、同じremove()
メソッドを使います。キャッシュのフラグメントを指定するキーは同じsf_cache_partial
のプレフィックス、モジュールの名前、アクションの名前とsf_cache_key/
で構成されます(キャッシュフラグメントのユニークな名前はcache()
ヘルパーに含まれます)。リスト12-11は例を示しています。
リスト12-11 - キャッシュから由来するテンプレートフラグメントをクリアする
[php]
<!-- Cached code -->
<?php if (!cache('users')): ?>
... // Whatever
<?php cache_save() ?>
<?php endif; ?>
// つぎのようにキャッシュに分類される
/sf_cache_partial/user/list/sf_cache_key/users
// つぎのコードでクリアする
$cacheManager->remove('@sf_cache_partial?module=user&action=list&sf_cache_key=users');
SIDEBAR クリアするキャッシュの選択に悩む
cache-clearing
ジョブのもっとも扱いにくい部分はデータの更新によってどのアクションが影響されるのかを決定することです。たとえば、現在のアプリケーションが
publication
モジュールを持つことを想像してください。そこでは著者の詳細情報(User
クラスのインスタンス)と一緒に、公開情報の一覧が表示され(list
アクション)説明されます(show
アクション)。1つのUser
レコードの修正はユーザーの公開情報の説明と公開情報の一覧に影響を与えます。このことは、つぎのように、user
モジュールのupdate
アクションを追加する必要があることを意味します:[php] $c = new Criteria(); $c->add(PublicationPeer::AUTHOR_ID, $this->getRequestParameter('id')); $publications = PublicationPeer::doSelect($c); $cacheManager = sfContext::getInstance()->getViewCacheManager(); foreach ($publications as $publication) { $cacheManager->remove('publication/show?id='.$publication->getId()); } $cacheManager->remove('publication/list');
HTMLのキャッシュを使い始めるとき、誤解されたリレーションが原因で新しいエラーが表示されないように、モデルとアクションの依存関係の展望をクリアに保つ必要があります。HTMLのキャッシュがアプリケーションのどこかで使われる場合、モデルを修正するすべてのアクションはおそらく
remove()
メソッドの一連の呼び出しを含むべきであることを覚えておいてください。そして、難しすぎる解析に悩みたくないのであれば、データベースを更新するたびにキャッシュ全体をつねにクリアできます・・・
アプリケーションのcache/
ディレクトリはつぎの構造を持ちます:
cache/ # sf_root_cache_dir
[APP_NAME]/ # sf_base_cache_dir
[ENV_NAME]/ # sf_cache_dir
config/ # sf_config_cache_dir
i18n/ # sf_i18n_cache_dir
modules/ # sf_module_cache_dir
template/ # sf_template_cache_dir
[HOST_NAME]/
all/
キャッシュされたテンプレートは[HOST_NAME]
ディレクトリ(ファイルシステムの互換性のためにドットはアンダースコアによって置き換えられる)の下、それらのURLに対応するディレクトリ構造に保存されます。たとえば、ページのテンプレートキャッシュはつぎのURLで呼び出され:
http://www.myapp.com/user/show/id/12
つぎの場所に保存されます:
cache/myapp/prod/template/www_myapp_com/all/user/show/id/12.cache
ファイルのパスをコードに直接書くべきではありません。代わりに、ファイルパスの定数を使うことができます。たとえば、現在の環境で現在のアプリケーションのtemplate/
ディレクトリへの絶対パスを読みとるには、sfConfig::get('sf_template_cache_dir')
を使います。
ディレクトリ構造を知っていることは手動によるキャッシュのクリアを処理するための助けになります。
複数のアプリケーションにまたがるキャッシュをクリアすることが問題になることがあります。たとえば、管理者がbackend
アプリケーションのuser
テーブルのレコードを修正する場合、frontend
アプリケーションのこのuser
テーブルに依存するすべてのアクションはキャッシュからクリアされる必要があります。remove()
メソッドは内部のURIを必要としますが、(アプリケーションはお互いに隔離されているため)アプリケーションはほかのアプリケーションのルーティングルールを知らないので、別のアプリケーションのキャッシュをクリアするためにremove()
メソッドを使うことはできません。
この問題の解決方法は、ファイルのパスに基づいて、cache/
ディレクトリからファイルを手作業で削除することです。たとえば、idが
12であるユーザーに対して
backendアプリケーションが``frontend
アプリケーション内部のuser/show
アクションのキャッシュをクリアする必要がある場合、つぎのコードを利用できます:
[php]
$sf_root_cache_dir = sfConfig::get('sf_root_cache_dir');
$cache_dir = $sf_root_cache_dir.'/frontend/prod/template/www_myapp_com/all';
unlink($cache_dir.'/user/show/id/12.cache');
しかし、これはとても満足のゆくものではありません。このコマンドは現在の環境のキャッシュだけを削除し、ファイルのパスに環境名と現在のホスト名を書くことを強いられます。これらの制限を回避するために、sfToolkit::clearGlob()
メソッドを利用できます。これはファイルのパターンをパラメーターとしてとり、ワイルドカードを受けとります。たとえば、ホストと環境に関係なく、同じキャッシュファイルを以前の例と同じようにクリアできます:
[php]
$cache_dir = $sf_root_cache_dir.'/frontend/*/template/*/all';
sfToolkit::clearGlob($cache_dir.'/user/show/id/12.cache');
特定のパラメーターにかかわらず、キャッシュされたアクションを削除する必要があるとき、このメソッドは多いに役立ちます。たとえば、アプリケーションがいくつかの言語を処理する場合、言語コードをすべてのURLに挿入することを選んだかもしれません。ユーザーのプロファイルページへのリンクはつぎのようになります:
http://www.myapp.com/en/user/show/id/12
すべての言語においてid
が12
であるユーザーのキャッシュされたプロファイルを除去するには、つぎのように簡単にできます:
[php]
sfToolkit::clearGlob($cache_dir.'/*/user/show/id/12.cache');
HTMLのキャッシュは、適切に処理されない場合、表示されるデータに矛盾が生じる可能性があります。要素に対してキャッシュを無効にするたびに、徹底的にテストし、調整するために実行ブーストのモニタリングを行います。
開発環境ではHTMLのキャッシュはデフォルトで無効なので、運用環境のキャッシュシステムは開発環境で検出できない新しいエラーに陥りがちです。なんらかのアクションのためにHTMLのキャッシュを有効にする場合、このセクションではステージング(staging)と呼ばれる新しい環境を作ります。ステージング環境はprod
環境と同じ設定(したがって、キャッシュが有効)ですが、web_debug
設定はon
です。
セットアップするには、アプリケーションのsettings.yml
ファイルを編集してリスト12-12の一番上に示されている行を追加します。
リスト12-12 - staging
環境のための設定(myapp/config/settings.yml
)
staging:
.settings:
web_debug: on
cache: on
加えて、運用環境のフロントコントローラー(おそらくmyproject/web/index.php
)の内容を新しいmyapp_staging.php
にコピーして新しいフロントコントローラーを作ります。つぎのようにSF_ENVIRONMENT
とSF_DEBUG
の値を変更します:
[php]
define('SF_ENVIRONMENT', 'staging');
define('SF_DEBUG', true);
作業はこれだけです。新しい環境が手に入りました。ドメイン名の後にフロントコントローラー名を追加して使います:
http://myapp.example.com/myapp_staging.php/user/list
TIP 既存のフロントコントローラーをコピーする代わりに、
symfony
のコマンドラインで新しいフロントコントローラーを作成できます。たとえば、myapp
アプリケーションに対して、SF_DEBUG
がtrue
でmyapp_staging.php
と呼ばれる、staging
環境を作成するには、symfony init-controller myapp staging myapp_staging true
を呼び出すだけです。
16章でWebデバッグツールバーとその内容について深く掘り下げて説明します。しかしながら、このツールバーはキャッシュの要素に関して有益な情報を提供します。キャッシュ機能の手短な説明はつぎのとおりです。
キャッシュ可能な要素(アクション、部分テンプレート、フラグメントなど)を含むページをブラウザーで閲覧するとき、図12-4で示されるようにWebデバッグツールバー(ウィンドウの右上のコーナー)が「ignore cache button」(緑で丸まった矢印)を表示します。このボタンはページをリロードして、キャッシュされた要素の処理を強制します。キャッシュはこのボタンでクリアされないことを覚えておいてください。
デバッグツールバーの右側の最後の数値はリクエストの実行期間です。ページ上でキャッシュを有効にした場合、この数値はページをロードする秒数を減らします。symfonyがスクリプトを再処理する代わりにキャッシュからデータを使うからです。このインディケータによって簡単にキャッシュの改善をモニタリングできます。
図12-4 - キャッシュを使うページのためのWebデバッグツールバー
デバッグツールバーはリクエストの処理の間に実行されたデータベースクエリの数も表示し、カテゴリごとの継続時間の詳細を示します(詳細を表示するtotal durationをクリックします)。このデータのモニタリングは、継続時間の合計と共に、キャッシュによってもたらされたすばらしいパフォーマンス改善の測定の助けになります。
多くの情報がWebデバッグツールバーで記録され利用可能になるので、デバッグモードはアプリケーションの速度を大きく減速させます。ですので、staging
環境でブラウジングしているとき、表示される処理時間はデバッグモードがoff
になっている運用環境で何が起きたのかを表していません。
それぞれのリクエストの処理時間のより優れた概要を知るには、Apache BenchやJMeterなどのベンチマーキングツールを使うべきです。これらのツールによってロードテストが可能になり2つの重要な情報の断片が提供されます: 2つの情報とは特定のページの平均的な読み込み時間とサーバーの最大キャパシティです。平均的な読み込み時間のデータはキャッシュの有効によるパフォーマンスの改善をモニタリングするためにとても役立ちます。
Webデバッグツールが有効にされたとき、図12-5で示されるように、キャッシュされた要素はそれぞれがトップ左側のキャッシュ情報のボックスを持つ赤いフレームによるページで識別されます。要素が実行された場合はボックスの背景が青色になり、ページがキャッシュから由来する場合は背景が黄色になります。キャッシュ情報をクリックするとキャッシュ要素の識別子、期限と最終修正以降の経過時間が表示されます。これは、文脈外の要素を処理するとき、要素が作られた時と実際にキャッシュできるテンプレートの部分を理解するために、問題を識別するための助けになります。
図12-5 - ページのキャッシュされた要素を識別する
HTTP 1.1プロトコルはブラウザーのキャッシュシステムをコントロールすることでアプリケーションを加速するために大いに役立つ一連のヘッダーを定義します。
World Wide Web Consortium(W3C, http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html)のHTTP 1.1の仕様はこれらのヘッダーを詳細に説明をしています(訳注:rfc2616の日本語訳)。アクションがキャッシュを有効にしていて、with_layout
オプションを使っている場合、アクションはつぎのセクションで説明される1つもしくは複数のメカニズムを利用します。
Webサイトのユーザーのブラウザーの一部がHTTP 1.1をサポートしていなかったとしても、HTTP 1.1のキャッシュ機能を使うことに関してリスクは存在しません。簡単に理解できないヘッダーを受けとるブラウザーはこれらの機能を無視するので、HTTP 1.1のキャッシュメカニズムをセットアップすることをお勧めします。
加えて、HTTP 1.1のヘッダーはプロキシサーバーとキャッシュサーバーも理解できます。ユーザーのブラウザーがHTTP 1.1を理解しない場合でも、それを利用するリクエストのroute内部でプロキシが存在できます。
ETag
機能が有効な場合、Webサーバーはレスポンス自身の署名を含む特別なヘッダーをレスポンスに追加します。
ETag: 1A2Z3E4R5T6Y7U
ユーザーのブラウザーはこの署名を保存し、同じページが必要になる次回にリクエストと一緒に再び送信します。新しい署名が最初のリクエスト以降にページが変更されていないことを示す場合、ブラウザーはレスポンスを返信しません。代わりに、304: Not modified
ヘッダーに送信できます。CPUの時間(この例ではgzipが有効)とサーバーのための帯域(ページの転送)とクライアントのための時間(ページの転送)が節約されます。全体で、ETag
ありのキャッシュ内のページはETag
なしのキャッシュ内のページよりもロードするのが速いです。
symfonyにおいて、アプリケーション全体に対してsettings.yml
のなかでETag
機能を有効にします。デフォルトのETag
設定はつぎのとおりです:
all:
.settings:
etag: on
レイアウトありのキャッシュ内のアクションのために、レスポンスはcache/
ディレクトリから直接とり込まれるので、処理はもっと速いです。
サーバーがレスポンスをブラウザーに送信するとき、ページに含まれるデータが最後に更新された時間を指定するために特別なヘッダーを追加できます:
Last-Modified: Sat, 23 Nov 2006 13:27:31 GMT
ブラウザーはこのヘッダーを理解できるので、再びページをリクエストするとき、それに合わせてIf-Modified
ヘッダーを追加します:
If-Modified-Since: Sat, 23 Nov 2006 13:27:31 GMT
サーバーはクライアントの値とアプリケーションによって返される値を比較できます。これらがマッチする場合、サーバーは304: Not modified
ヘッダーを返し、ETag
と同じように帯域とCPUの時間を節約します。
symfonyにおいて、別のヘッダーに対して行うことと同じように、Last-Modified
応答ヘッダーを設定できます。たとえば、これをアクションのなかで利用できます:
[php]
$this->getResponse()->setHttpHeader('Last-Modified', $this->getResponse()->getDate($timestamp));
この日付は、データベースもしくはファイルシステムから渡される、ページで使われるデータの実際の最終更新日になります。sfResponse
オブジェクトのgetDate()
メソッドはタイムスタンプをLast-Modified
ヘッダー(RFC1123)に対して必要な書式の日付に変換します。sfResponse
オブジェクトのgetDate()
メソッドはタイムスタンプをLast-Modified
ヘッダー(RFC1123)のために必要な書式の日付に変換します。
HTTP 1.1の別のヘッダーはVary
です。このヘッダーはページが依存するパラメーターがどれなのかを定義し、キャッシュのキーを作成するためにブラウザーとプロキシによって使われます。たとえば、ページの内容がCookieに依存する場合、Vary
ヘッダーをつぎのように設定できます:
Vary: Cookie
たいていの場合、アクション上でキャッシュを有効にすることは難しいです。なぜならページがCookie、ユーザーの言語、もしくはその他にしたがって変わるかもしれないからです。キャッシュのサイズを増やすことをいとわないのであれば、レスポンスのVary
ヘッダーを適切に設定してください。つぎのようなcache.yml
設定ファイルもしくはメソッドに関連するsfResponse
を使うことで、この設定はアプリケーション全体のため、もしくはアクションをベースに行うことができます。:
[php]
$this->getResponse()->addVaryHttpHeader('Cookie');
$this->getResponse()->addVaryHttpHeader('User-Agent');
$this->getResponse()->addVaryHttpHeader('Accept-Language');
symfonyはこれらのパラメーターの値ごとに異なるバージョンのページをキャッシュに保存します。これはキャッシュのサイズを増やしますが、サーバーがこれらのヘッダーにマッチするリクエストを受けとるたびに、レスポンスは処理される代わりにキャッシュからとり込まれます。リクエストのヘッダーだけにしたがって変化するページのための偉大なパフォーマンスツールです。
これまで、ヘッダーを追加することによって、キャッシュされたバージョンのページ保持する場合でも、ブラウザーはサーバーへのリクエストの送信を続けました。Cache-Control
ヘッダーとExpires
ヘッダーをレスポンスに追加することでこれを避けることができます。PHPにおいてこれらのヘッダーはデフォルトで無効ですが、サーバーへの不要なリクエストを避けるためにsymfonyはこれらのふるまいをオーバーライドできます。
通常は、sfResponse
オブジェクトのメソッドを呼び出すことでこのふるまいを起動させます。アクションにおいて、ページがキャッシュされる最大時間(秒)を定義します:
[php]
$this->getResponse()->addCacheControlHttpHeader('max_age=60');
ページがキャッシュされる条件も指定できるので、提供者のキャッシュは(銀行口座の番号のように)プライベートなデータのコピーを保存しません:
[php]
$this->getResponse()->addCacheControlHttpHeader('private=True');
HTTPのCache-Control
ディレクティブを使うことで、サーバーとクライアントブラウザー間でのさまざまなキャッシュメカニズムを調整する機能が得られます。これらのディレクティブの詳細な概説については、W3CのCache-Control
の仕様をご覧ください。
最後の1つのExpires
ヘッダーはsymfonyを通して設定できます:
[php]
$this->getResponse()->setHttpHeader('Expires', $this->getResponse()->getDate($timestamp));
CAUTION
Cache-Control
のメカニズムを有効にした場合、主な結果はサーバーのログがユーザーによって発せられたすべてのリクエストではなく、実際に受けとったリクエストのみを表示するようになることです。パフォーマンスがよくなれば、統計データではサイトの見た目の人気は減ることがあります。
選択したキャッシュタイプにしたがって、キャッシュシステムは可変のパフォーマンスの加速を提供します。ゲインが最大から最小のものから、キャッシュタイプはつぎのものがあります。
加えて、部分テンプレートとコンポーネントも同様にキャッシュされます。
モデルもしくはセッション内のデータを変更することで一貫性のためにキャッシュを削除することが強制される場合、あなたはきめ細かい粒度で行うことができます。変更された要素のみが削除され、そのほかのものは保たれます。
あなたが間違った要素をキャッシュする場合、もしくは内在するデータを更新したときにキャッシュをクリアすることを忘れた場合、新しいバグが現れるかもしれないので、 キャッシュが有効にされたすべてのページを注意深くテストすることを忘れないでください。ステージング(staging)環境は、キャッシュのテスト専用で、そのために大いに役立ちます。
最後に、HTTP 1.1のプロトコルをsymfonyの高度なキャッシュ調整機能のなかで最大限活用してください。このことによってキャッシュタスクのなかでクライアントに影響を与えさらなるパフォーマンスのゲインを提供することになります。