トップ  > symfony  > いろいろ  > 記事

No.1671 conditionalCacheFilterをより一般的に考えてみる

conditionalCacheFilterをより一般的に考えてみる

最初に

symfonyでアプリケーションを作ろうとする際にキャッシュを使っていますか?キャッシュと一言に言ってもいろんなレイヤーでキャッシュを実現する方法があるので、なんとも答えに困る質問ですね。しかし、webアプリを作る際に言われているキャッシュであれば、

  1. apcなどでphpスクリプトをキャッシュ
  2. ページ出力内容を静的ファイルもしくは、メモリ上にキャッシュ
  3. クライアントのブラウザ側に残してもらうキャッシュ

等が一般的に考えられるのではないかと思います。おそらく、opcodeに関しては、多くの人はapcなどを使うとしても、チューニングの設定等を 意識する必要はあると思いますが、実際に中でどう動いているのかを考えることが少ないと思います。「普通のやつらの下を行く」ことを目的とするユーザ(下 のレイヤーで勝負するという意味)はそちらを調べてもらうとして、Webアプリ作成屋としては一番腕の見せ所は2番目ではないかと思います。つまり、どの 出力をキャッシュ化して、どの出力をキャッシュ化しない、といったようなことです。そして、キャッシュを絶妙のタイミングでクリアさせることです。これに よって、データベースへの接続の際のクエリーが激減させることができますので、大幅なパフォーマンスの改善が望めるからです。

今回のポストでは、この2番目の内容をもう少し詳細まで扱おうと思います。

問題

symfonyでは、cacheをサポートしており、cache.ymlで指定させることによって、次に上げるようなページキャッシュが可能となっています。

  1. layoutを含むキャッシュなのか、そうでないのか、という指定
  2. キャッシュの有効期間の指定
  3. partial, component等のページ全体ではなくて、一部のみのキャッシュの指定

これでだいたいの場合はキャッシュの使用として解決ができると思います。しかし実際にアプリケーションを作成していると、もう少し詳細なキャッシュ機構があったらいいな、というときがあります。たとえば、Jobeetのfeedの章で あったようなことです。つまり、同じactionを使用するが、sf_formatの値によって、出力をatomにするのか、htmlにするのか、切り分 ける場合です。atomの出力とhtmlの出力で同じキャッシュの指定をしたいですか?もし、同じでいいのであればいいのですが、私は分けたいと思いまし た。htmlの方ではログインしているかどうかも関係してくるのですが、atomの場合は、ログインしていようがいまいが同じ出力を返すからです。

同様に、ログインしているかどうかによって切り分けるページもあるでしょう。例えば、ログインしていると、ヘッダに、「hogehogeさん」って名前が出てくるようなものです。見せるコンテンツはログインしていようがいまいが同じだけども、ログインしていない際にはページ全体をキャッシュさせたいと思いますし、ログインしていたら、キャッシュ化させる内容は分けたいと思います。現在は、同じページでwith_layoutとwithout_layoutで切り分けることはできません。なので、ログインしてない際にはキャッシュを有効にさせ、ログインしている際にはページを無効にさせるという方法を取ります。

これらの内容は、cache.ymlだけでは実現が不可能ですので、他の方法が必要となります。

方法

symfonyにおいて、キャッシュを使う方法はいくつか用意されています。一番大きな方法としては、cache.ymlで指定することです。次に viewの中でキャッシュを使用するかどうかを指定することでしょう。さらにcacheManagerを通してキャッシュのロジックを変更することができ ます。キャッシュを自分の好きなタイミングでクリアしたり、有効にしたりすることができます。このcacheManagerを通してキャッシュを使用する には、filterが一番相性がいいようです。クリアするのは、データの変更後などになりますので、actionに書くことになってしまいますが、 requestによってキャッシュを使用するしないを変更するにはfilterを使うことになります。The Definitive Guilde to symfonyのcacheの章のconditionalCacheFilterのような方法です。今回のポストでは、このconditionalCacheFilterをもう少し拡張してみることにします。

ソースコード

今回使用したsymfonyのバージョンは、1.3.0-ALPHA2です。1.2系は同じようにできると思いますが、1.0系はおそらく実装が異なると思います。

/apps/your_apps/config/filters.yml

01.rendering: ~
02.security:  ~
03. 
04.# insert your own filters here
05.conditionalCacheFilter:
06.  class: conditionalCacheFilter
07.  param:
08.    pages:
09.#      - { module: post, action: index, format: [atom,xml] }
10.#      - { module: post, action: index, format: [xml], enabled: false }
11.      - { module: post, format: [xml], enabled: true , is_authenticated: false }
12. 
13.cache:     ~
14.execution: ~

/apps/your_app/lib/conditionalCacheFilter.class.php

01.class conditionalCacheFilter extends sfFilter
02.{
03.  protected
04.    $cacheManager    = null,
05.    $request         = null,
06.    $user            = null,
07.    $uri             = null,
08.    $defaultLifetime = null;
09. 
10.  public function initialize($context, $parameters = array())
11.  {
12.    parent::initialize($context, $parameters);
13. 
14.    $this->cacheManager    = $context->getViewCacheManager();
15.    $this->request         = $context->getRequest();
16.    $this->user            = $context->getUser();
17.    $this->uri             = $context->getRouting()->getCurrentInternalUri();
18. 
19.    $lifetime = (isset($this->cacheManager)) ? $this->cacheManager->getLifeTime($this->uri) : 0;
20.    $this->defaultLifetime = ($lifetime > 0) ? $lifetime : 86400;
21.  }
22. 
23.  public function execute($filterChain)
24.  {
25.    if ((!isset($this->cacheManager)) or !($this->getParameter('pages', false))) {
26.      $filterChain->execute();
27.      return;
28.    }
29. 
30.    foreach ($this->getParameter('pages') as $page) {
31. 
32.      $module = isset($page['module']) ? $page['module'] : null;
33. 
34.      $action = isset($page['action']) ? $page['action'] : $this->request->getParameter('action');
35. 
36.      $format = isset($page['format']) ? $page['format'] : array();
37.      if (!is_array($format)) {
38.        $format = array($format);
39.      }
40. 
41.      $is_authenticated = isset($page['is_authenticated']) ? $page['is_authenticated'] : null;
42. 
43.      // maybe better to throw exception?
44.      if (is_null($module)) {
45.        continue;
46.      }
47.      // skip
48.      if ($module !== $this->request->getParameter('module')) {
49.        continue;
50.      }
51.      // skip
52.      if (!empty($format) and !in_array($this->request->getParameter('sf_format', 'html'), $format)) {
53.        continue;
54.      }
55. 
56.      if (is_null($is_authenticated) or ($is_authenticated === $this->user->isAuthenticated())) {
57.        // remove
58.        if (isset($page['enabled']) and $page['enabled'] === false) {
59.          $this->cacheManager->remove($this->uri);
60.          continue;
61.        }
62. 
63.        //add
64.        $this->cacheManager->addCache($module, $action, array(
65.          'lifeTime' => isset($page['lifetime']) ? $page['lifetime'] : $this->defaultLifetime
66.        ));
67.      }
68.    }
69. 
70.    $filterChain->execute();
71.  }
72.}

解説

filters.ymlにて、cache filterが通る前にconditionalCacheFilterを呼びます。filterに渡すことのできるパラメターとしては、The Definitive Guilde to symfonyのcacheの章に あるようにpagesとします。そして、そこに配列として指定したいモジュール名とアクション(ない場合はそのモジュールに属すすべてのアクショ ン)format, enabled, is_authenticated, lifetimeなどのパラメターを指定できるようにしました。is_authenticatedに関しては3つのパターンがありますね。つまり、認証済 みの場合、非認証の場合、認証しているかどうかは関係のない場合です。

また、symfony1.3からは、yamlのバージョンの扱いが1.2になりますので、on/off, yes/noは使えなくなりますので、気をつけてください。true/falseで切り分けるようにしましょう。

そして、conditionalCacheFilter.class.phpにおいては、それぞれのパラメターによってキャッシュの使用について分 けるようにしました。cacheManagerに関しましては、キャッシュを有効化していないとnullを持ちますので、そのチェックが必要です。デフォ ルトではenableとなっているページもこのfilters.ymlでenabledをfalseに指定することにより、毎回キャッシュをクリアするよ うにできますので、より柔軟にキャッシュの変更ができるようになっています。

まとめ

今回のポストでは、symfonyのキャッシュフレームワークを使用する際に役に立つtipsとしてconditionalCacheFilterを一般的な使用方法で使えるように拡張しました。この拡張によって、次の2つが可能となりました。

  1. sf_formatによってキャッシュをするかどうかを切り分ける
  2. ユーザが認証済みかどうかでキャッシュをするかどうかを切り分ける

sf_formatによってキャッシュをするかどうかを切り分けることによって、例えば、feedのようなxml形式のものはある程度キャッシュを しつつ、アプリケーションとしてwebで表示されるviewであるhtml形式のものはキャッシュを使用しなくするということが可能となります。xml以 外にも考えられるものはjsonなどのAPI提供です。これらは特別な理由がない限りキャッシュ化されたものを返すのがいいと考えています。

また、ユーザが認証済みかどうかでキャッシュをするかどうかを切り分けることができることにより、認証をしていないユーザに返すレスポンスに速くな ることでしょう。これはWebサイトに訪問してくれたユーザのみならず、Googleなどの検索エンジンのクローラーにも言えることです。クローラーに よってアクセスさせるものに毎回DBアクセスをさせるのではなく、キャッシュを返して、定期的にそのキャッシュを更新させることによって、負荷を下げま しょう。

拡張しようと思えば、その余地を残してくれているのがsymfonyフレームワークです。今回のconditonalCacheFilterは、特 定のアプリケーションに依存するというよりは、より一般的なアプリケーションに必要な機能だと考えていますので、plugin化して、置いておくのもいい かもしれません。もちろんバグがあることもあると思いますので、それに関しては教えてください。

引用元

更新:2009/10/26 08:56 カテゴリ: symfony  > いろいろ ▲トップ

FuelPHP

Mac

フロントエンド開発

web開発

プロマネ

マネタイズ

プレゼン

webサービス運用

webサービス

Linux

サーバ管理

MySQL

ソース・開発

svn・git

PHP

HTML・CSS

JavaScript

ツール, ライブラリ

ビジネス

テンプレート

負荷・チューニング

Windows

メール

メール・手紙文例

CodeIgniter

オブジェクト指向

UI・フロントエンド

cloud

マークアップ・テキスト

Flash

デザイン

DBその他

Ruby

PostgreSQL

ユーティリティ・ソフト

Firefox

ハードウェア

Google

symfony

OpenPNE全般

OpenPNE2

Hack(賢コツ)

OpenPNE3

リンク

個人開発

その他

未確認

KVS

ubuntu

Android

負荷試験

オープンソース

社会

便利ツール

マネー

Twig

食品宅配

WEB設計

オーディオ

一般常識

アプリ開発

Python

サイトマップ

うずら技術ブログ

たませんSNS

rss2.0