アプリケーションを加速する方法の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 - キャッシュを有効にする (frontend/config/settings.yml
)
dev:
.settings:
cache: on
静的な情報 (データベースに依存しないもしくはセッションに依存するデータ) を表示するアクション、もしくは修正なしでデータベースから情報を読みとるアクション (典型的なものは GET リクエスト) はしばしばキャッシュのために理想的です。図12-1はこの場合においてページ要素がキャッシュされることを示しています: アクションの結果 (そのテンプレート) もしくはレイアウトとのアクションの結果です。
図12-1 - アクションをキャッシュする
たとえば、Web サイトのすべてのユーザーリストを返す user/list
アクションを考えてください。ユーザーが修正、追加、もしくは除去(そしてこのことが"キャッシュから項目を除去する"のセクションで検討されます)されないかぎり、このリストはつねに同じ情報を示しますので、キャッシュのよい候補です。
キャッシュの有効と設定、アクションによるアクションはモジュールの config/
ディレクトリに設置される cache.yml
ファイルで定義します。例としてリスト12-2をご覧ください。
リスト12-2 - アクションのためにキャッシュを有効にする (frontend/modules/user/config/cache.yml
)
list:
enabled: on
with_layout: false # デフォルト値
lifetime: 86400 # デフォルト値
この設定は、list
アクションのためにキャッシュがオンになっていること、およびレイアウトがアクション (デフォルトのふるまい) によってキャッシュされないことを保証します。このことは、キャッシュ済みのアクションが存在する場合でも、(部分テンプレートとコンポーネントと一緒に) レイアウトは再度処理されることを意味します。with_layout
設定が true
にセットされる場合、レイアウトはアクションによってキャッシュされ、再度処理されることはありません。
キャッシュの設定をテストするには、ブラウザーから開発環境のアクションを呼び出します。
http://myapp.example.com/frontend_dev.php/user/list
ページのアクション領域周辺の境界に注目してください。最初に、領域は青色のヘッダーで、ページがキャッシュから由来するものではないことを示します。ページをリフレッシュすると、アクションの領域が黄色のヘッダーを示し、キャッシュから由来するものであることを表します (レスポンスの時間単位で目立って加速されます)。後の章でキャッシュのテストとモニタリングを行う方法について詳しく学ぶことになります。
NOTE スロットはテンプレートの一部で、アクションをキャッシュすることはこのアクションのテンプレートのなかで定義されたスロットの値を保存することにもなります。ですのでキャッシュはスロットに対してネイティブに機能します。
キャッシュシステムは引数を持つページに対しても機能します。user
モジュールが、たとえば、ユーザーの詳細を表示するために id
引数を必要とする show
アクションを持つとします。リスト12-3で示されるように、このアクションに対してキャッシュを有効にするには cache.yml
ファイルを修正します。
cache.yml
の内容を見やすくするために、リスト12-3でも示されるように、all:
キーの下でモジュールのすべてのアクションのための設定を再編成することができます。
リスト12-3 - 全内容の例 (frontend/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 - 部分テンプレートのキャッシュ設定 (frontend/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つのインクルードのタイプは部分テンプレートとよく似ており、同じ方法でキャッシュをサポートします。たとえば、現在の日時を表示するために、グローバルレイアウトが day
と呼ばれるコンポーネントを include_component('general/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()
ヘルパーを使う (frontend/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
のまえに実行されるフィルターです。本当に、セキュリティ機能と同じように、キャッシュはsymfonyのフィルターです。ユーザーが認証されていない場合だけ article/show
ページに対してキャッシュを有効にするには、リスト12-6で示されるように、アプリケーションの lib/
ディレクトリのなかで conditionalCacheFilter
を作成します。
リスト12-6 - PHP を使ってキャッシュを設定する (frontend/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で示されるように、filters.yml
ファイルのなかの sfCacheFilter
のまえにこのフィルターを登録しなければなりません。
リスト12-7 - カスタムフィルターを登録する (frontend/config/filters.yml
)
...
security: ~
conditionalCache:
class: conditionalCacheFilter
param:
pages:
- { module: article, action: show }
cache: ~
...
キャッシュをクリアすれば (新しいフィルタークラスがオートロードされるため)、条件つきのキャッシュお準備ができます。これは、認証されていないユーザーに対してのみ、pagesパラメーターで定義されたページのキャッシュを有効にします。
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 の代替クラスの1つ (sfAPCCache
、sfEAcceleratorCache
、sfMemcacheCache
、sfSQLiteCache
とsfXCacheCache
を含む) に置き換えることができます。param
キーの下で定義されたパラメーターは連想配列としてカスタムクラスのinitialize()
メソッドに渡されます。どのビューキャッシュストレージクラスもsfCache
抽象クラスのメソッドをすべて実装しなければなりません。この主題に関する詳しい情報は19章を参照してください。
キャッシュされたページでさえいくつかの 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 コマンドラインの cache:clear
タスクはキャッシュ (HTML、設定、ルーティングと国際化のキャッシュ) を削除します。リスト12-8で示されるように、キャッシュの部分集合だけを削除するためにこれを引数に渡すことができます。symfony プロジェクトのルートからのみ呼び出せることを覚えておいてください。
リスト12-8 - キャッシュをクリアする
// キャッシュ全体を消去する
> php symfony cache:clear
// 短い構文
> php symfony cc
// frontend アプリケーションのキャッシュのみを削除する
> php symfony cache:clear --app=frontend
// myapp アプリケーションの HTML キャッシュだけを削除する
> php symfony cache:clear --app=frontend --type=template
// frontend アプリケーションの設定のキャッシュのみを削除する
// 組み込みのタイプは config、i18n、routing と template。
> php symfony cache:clear --app=frontend --type=config
// frontend アプリケーションと prod 環境のコンフィギュレーションキャッシュのみを削除する
> php symfony cache:clear --app=frontend --type=config --env=prod
データベースが更新されたとき、修正されたデータに関連するアクションのキャッシュをクリアしなければなりません。キャッシュ全体をクリアできましたが、モデルの変更に関係のない既存のすべてのキャッシュされたアクションに対しては無駄な作業です。これが 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(sfWebRequest $request)
{
// ユーザーを更新する
$user_id = $request->getParameter('id');
$user = UserPeer::retrieveByPk($user_id);
$this->foward404Unless($user);
$user->setName($request->getParameter('name'));
...
$user->save();
// このユーザーに関連するアクションに対するキャッシュをクリアする
$cacheManager = $this->getContext()->getViewCacheManager();
$cacheManager->remove('user/list');
$cacheManager->remove('user/show?id='.$user_id);
...
}
キャッシュされた部分テンプレート、コンポーネント、コンポーネントスロットを除去する作業は少々複雑です。これらに任意のタイプのパラメーター(オブジェクトを含む)を渡すことができるので、あとでキャッシュバージョンを区別するのが不可能だからです。部分テンプレートに焦点を当てて説明します。このしくみはほかのテンプレートのコンポーネントについても同様です。特別なプレフィックス (sf_cache_partial
)、モジュールの名前、部分テンプレートの名前および呼び出すために使われるすべてのパラメーターのハッシュで、symfony はキャッシュされた部分テンプレートを区別します:
[php]
// 呼び出される部分テンプレート
<?php include_partial('user/my_partial', array('user' => $user) ?>
// はつぎのようなキャッシュに分類される
@sf_cache_partial?module=user&action=_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?module=user&action=_my_partial&sf_cache_key=12
// つぎのコードでキャッシュの特定のユーザーに対する _my_partial をクリアする
$cacheManager->remove('@sf_cache_partial?module=user&action=_my_partial&sf_cache_key='.$user->getId());
テンプレートフラグメントをクリアするには、同じ 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?module=user&action=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, $request->getParameter('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()
メソッド呼び出しを格納すべきであることを覚えておいてください。そして、難しすぎる解析に悩みたくないのであれば、データベースを更新するたびにキャッシュ全体をクリアできます・・・
remove()
メソッドはワイルドカードを持つキーを受けとります。これによって単独の呼び出しでいくつかのキャッシュを削除できるようになります。たとえばつぎのように行うことができます:
[php]
$cacheManager->remove('user/show?id=*'); // 現在のホストのすべてのユーザーのレコードのキャッシュを削除する
別のよい例は、すべての URL のなかで言語コードが示される複数の言語を扱うアプリケーションです。ユーザープロファイルのページへの URL はつぎのようになります:
http://www.myapp.com/en/user/show/id/12
id
が 12
であるユーザープロファイルのキャッシュを削除するには、つぎのように簡単に呼び出すことができます:
[php]
$cache->remove('user/show?sf_culture=*&id=12');
これは部分テンプレートに対しても機能します:
[php]
$cacheManager->remove('@sf_cache_partial?module=user&action=_my_partial&sf_cache_key=*'); // すべてのキーに対して除去する
remove()
メソッドは2つの追加パラメーターを受けとり、キャッシュをクリアしたいホストと vary ヘッダーを定義できるようにします。symfony はそれぞれのホストと vary ヘッダーに対して1つのキャッシュバージョンを保存するので、同じコードベースを共有するが同じホスト名を共有しない2つのアプリケーションは異なるキャッシュを使います。これはアプリケーションがサブドメイン (たとえば http://php.askeet.com
とhttp://life.askeet.com
) をリクエストパラメーターとして解釈するとき、とても便利です。最後の2つのパラメーターを設定しない場合、symfony は現在のホストと all
の vary ヘッダーに対してキャッシュを除去します。代わりに、別のホストに対してキャッシュを除去したい場合、つぎのようにremove()
を呼び出します:
[php]
$cacheManager->remove('user/show?id=*'); //現在のホストのすべてのユーザーのキャッシュレコードを削除する
$cacheManager->remove('user/show?id=*', 'life.askeet.com'); // life.askeet.com のホストのすべてのユーザーのキャッシュレコードを削除する
$cacheManager->remove('user/show?id=*', '*'); //すべてのホストのすべてのユーザーのキャッシュレコードを削除する
remove()
メソッドは factories.yml
で定義できるすべてのキャッシュストレージで機能します (sfFileCache
だけでなく、sfAPCCache
、 sfEAcceleratorCache
、sfMemcacheCache
、sfSQLiteCache
と sfXCacheCache
も)。
複数のアプリケーションにまたがるキャッシュをクリアすることが問題になる可能性があります。たとえば、管理者が backend
アプリケーションの user
テーブルのレコードを修正する場合、frontend
アプリケーションのこのユーザーに関係するすべてのアクションはキャッシュからクリアされる必要があります。しかし backend
アプリケーションが利用できるビューキャッシュマネージャーは frontend
アプリケーションのルーティングルールがわかりません (アプリケーションはそれぞれ独立しています)。ですので backend
アプリケーションのなかでつぎのコードを書くことはできません:
[php]
$cacheManager = sfContext::getInstance()->getViewCacheManager(); // backend アプリケーションのビューキャッシュマネージャを読みとる
$cacheManager->remove('user/show?id=12'); // テンプレートは frontend アプリケーション内に存在するので、パターンは見つからない
解決策は、frontend キャッシュマネージャーと同じ設定で、手動で sfCache
オブジェクトを初期化することです。幸いにして、symfony 内のすべてのキャッシュクラスはビューキャッシュマネージャーの remove
と同じサービスを供与する removePattern
メソッドを提供します。
たとえば、backend
アプリケーションが id
が 12
であるユーザーに対して frontend
アプリケーションのなかで user/show
アクションのキャッシュをクリアしたい場合、つぎのコードを利用できます:
[php]
$frontend_cache_dir = sfConfig::get('sf_cache_dir').DIRECTORY_SEPARATOR.'frontend'.DIRECTORY_SEPARATOR.sfConfig::get('sf_environment').DIRECTORY_SEPARATOR.'template';
$cache = new sfFileCache(array('cache_dir' => $frontend_cache_dir)); //frontend の factories.yml で 定義される設定と同じものを使う
$cache->removePattern('user/show?id=12');
異なるキャッシュ戦略に対して、キャッシュオブジェクトの初期化を変更することだけが必要ですが、キャッシュを削除する方法は変わりません:
[php]
$cache = new sfMemcacheCache(array('prefix' => 'frontend'));
$cache->removePattern('user/show?id=12');
HTML キャッシュは、適切に処理されない場合、表示されるデータに矛盾が生じる可能性があります。要素に対してキャッシュを無効にするたびに、徹底的にテストし、調整するために実行ブーストのモニタリングを行います。
開発環境では HTML キャッシュはデフォルトで無効なので、運用環境のキャッシュシステムは開発環境で検出できない新しいエラーに陥りがちです。なんらかのアクションのために HTML キャッシュを有効にする場合、このセクションではステージング (staging) と呼ばれる新しい環境を作ります。ステージング環境は prod
環境と同じ設定 (したがって、キャッシュが有効) ですが、web_debug
設定は on
です。
セットアップするには、アプリケーションの settings.yml
ファイルを編集してリスト12-12の一番上に示されている行を追加します。
リスト12-12 - staging
環境のための設定 (frontend/config/settings.yml
)
staging:
.settings:
web_debug: on
cache: on
加えて、運用環境のフロントコントローラー (おそらく myproject/web/index.php
) を新しい frontend_staging.php
にコピーして新しいフロントコントローラーを作ります。getApplicationConfiguration()
メソッドに渡される引数を変更するためにこのファイルをつぎのように編集します:
[php]
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'staging', true);
作業はこれだけです。新しい環境が手に入りました。ドメイン名の後にフロントコントローラー名を追加して使います:
http://myapp.example.com/frontend_staging.php/user/list
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 を理解しない場合でも、それを利用するリクエストのルート内部でプロキシが存在できます。
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) のために必要な書式の日付に変換します。
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 の高度なキャッシュ調整機能のなかで最大限活用してください。このことによってキャッシュタスクのなかでクライアントに影響を与えさらなるパフォーマンスのゲインを提供することになります。