トップ  > メモ一覧  > カテゴリ「チューニング」の絞り込み結果 : 2件

2件中 1 〜 2 表示  1 

No.2437 関数の呼び出し結果をキャッシュする(ファンクションキャッシュ)

関数の呼び出し結果をキャッシュする

関数がコンテキスト依存の値もしくはランダム性に依存しない場合、その関数を同じパラメーターで2回呼び出すと同じ値が戻ります。このことは最初に結果を保存していれば2番目の呼び出しは十分に回避できたことを意味します。sfFunctionCacheクラスが担う仕事はまさにこれです。このクラスのcall()メ ソッドは引数としてcallableとパラメーターの配列を必要とします。呼び出されたとき、 このメソッドはすべての引数でmd5ハッシュを作りキャッシュのなかでこのハッシュで名づけられたキーを探します。ファイルが見つかれば、関数はファイル に保存された結果を返します。そうでなければ、sfFunctionCacheクラスが関数を実行し、結果をキャッシュに保存し、それを返します。リスト18-16の2番目の関数の実行は最初のものより速いです。

リスト18-16 - 関数の結果をキャッシュする

$cache = new sfFileCache(array('cache_dir' => sfConfig::get('sf_cache_dir').'/function'));
$fc = new sfFunctionCache($cache);
$result1 = $fc->call('cos', array(M_PI));
$result2 = $fc->call('preg_replace', array('/\s\s+/', ' ', $input));

sfFunctionCacheのコンストラクターはキャッシュオブジェクトを必要とします。call()メソッドの最初の引数はcallableでなければならないので、関数名、クラス名とstaticメソッド名の配列、もしくはオブジェクト名とpublicなメソッド名の配列でなければなりません。call()メソッドの別の引数に関して、これはcallableに渡される引数の配列です。

例に関してキャッシュオブジェクトに基づいてファイルを使う場合、cache/ディレクトリの元にキャッシュディレクトリを置くほうがよいです。cache:clearタスクで自動的にキャッシュが一掃されるからです。関数キャッシュをほかのどこかに保存する場合、コマンドラインを通してキャッシュをクリアするときこれは自動的にクリアされません。


■memcacheに保存する場合
sfFileCache → sfMemcacheCache



引用元

更新:2010/05/06 21:29 カテゴリ: symfony  > チューニング ▲トップ

No.1964 symfonyのルーティングでメモリが肥大化する問題と対処法

symfonyのルーティングでメモリが肥大化する問題と対処法Add Starrgfxaki77


先日、symfony(v1.2.7)で本番(prod)環境に設定した場合に開発(dev)環境の数倍のメモリが消費されるという状況に陥ってしまいました。原因を追及した結果、ルーティングの設定に問題があることが発覚したので、今日はそのことを書こうと思います。

原因先には述べてあるとおり、ルーティングの設定に問題がありました。symfony1.2ではルーティングのキャッシュということを行っており、そのキャッシュが肥大化してメモリを大量に消費する原因となっていました。
対策としてルーティングのキャッシュを無効にしてキャッシュファイルの読み書きを行わないように設定ファイルを修正したところ、上記の問題は無事に解決しました。

  1. 追記@2009/09/28
  2. symfony1.2.9以降では初期状態でキャッシュが無効になるように設定されています。symfony1.2.9からのgenerate:appタスクでアプリケーションの生成を行った場合は修正は必要ありません。

というわけで今回は、

1. ルーティングのキャッシュの仕組み
2. なぜそんなにもキャッシュが肥大化してしまったのか
3. ルーティングのキャッシュを無効にする方法、その他対策

上記の3つをテーマにお話しさせていただきます。

symfony 1.2のルーティングについては以前書いた「symfony 1.2のルーティングまとめ」という記事をご覧ください。
symfonyでは通常、 /モジュール名/アクション名(/パラメータ) という形式のURLをとりますが、このURLとモジュールおよびアクションを結びつける仕組みをルーティングと呼んでいます。例えば、/article /newというURLはarticleモジュールのnewアクション、といったものです。

そしてそれぞれのルーティングには名前がついています。/モジュール名/アクション名(/パラメータ) という形式はdefaultという名前がつけられています。これ以外にもホームページにあたるhomepageや各モジュールのインデックスにあたる default_indexがデフォルトで定義されています。

実際に symfony generate:app コマンドでアプリケーションを作った際に、routing.ymlには以下のような定義がされています。

  1. homepage:
  2.   url:   /
  3.   param: { module: default, action: index }
  4.  
  5. default_index:
  6.   url:   /:module
  7.   param: { action: index }
  8.  
  9. default:
  10.   url:   /:module/:action/*

:(コロン)ではじまる項目はリクエストパラメータとして扱われます。先ほど例に挙げた/article/newというURLはdefaultにマッチ し、$request->getParameter('module')としてリクエストオブジェクトからパラメータを取得すればarticleと いう文字列が取れます。moduleとactionは特別なパラメータで、名前の通りモジュール名とアクション名を特定するための値です。
またhomepageやdefault_indexでparamという項目の中でも同じようにmoduleやactionが指定されていますが、これはURLがマッチした際にURLに含まれていなくてもリクエストパラメータとして指定するというものです。

*(アスタリスク)は任意のリクエストパラメータを/(スラッシュ)区切りで記述可能にするもので、 /article/show/year/2009/month/08/day/20 というURLでアクセスが来た場合にarticleモジュールのshowアクションが呼び出され、リクエストオブジェクトからはそれぞれyearが 2009, monthが08, dayが20のように取得することができます。/article/show?year=2009&month=08&day=20 と同じようなものですが、スラッシュ区切りで来ている場合は$_GETには直接はいらず、symfonyが内部でリクエストに定義するということを行うと いう違いがあります。

もし /article/2009/08/20 というURLでarticleモジュールのshowアクションに行くようにし、2009, 08, 20という値をそれぞれyear, month, dayという名前のリクエストパラメータとして指定したい場合は以下のようにrouting.ymlに定義します。

  1. article_show_at_date:
  2.   url:   /article/:year/:month/:day
  3.   param: { action: show }

これでarticle_show_at_dateがルーティングに定義されます。このルーティングをアプリケーション内から呼び出すときにはlink_toやurl_forといったヘルパーや、アクションのredirectメソッドがよく知られていると思います。
例えばurl_forであれば url_for('@article_show_at_date?year=2009&month=08&day=20') のように指定します。実際にurl_forやredirectがURLのパースを行う処理はsfWebControllerのgenUrlメソッドが呼び 出されており、更にその中でsfPatternRoutingクラスのgenerateメソッドを呼び出しており、パース処理が行われています。

このパース処理ですが、routing.ymlを展開した後、各ルーティングを分解して正規表現に変換して1つ1つマッチングを行うなどの処理をして、最終的にURLに変換を行うという流れになります。
この @article_show_at_date?year=2009&month=08&day=20 を /article/2009/08/20 に変換する処理をキャッシュすることがルーティングのキャッシュになります。実装方法は単純で、前者の内部的なURLにコンテキストの情報を付与したもの をキーにしてキャッシュを作成する実装になっています。

キャッシュのキーを具体的に生成してみます。

  1. // ルーティングの名前
  2. string 'article_show_at_date' (length=20)
  3.  
  4. // ルーティングのパラメータ
  5. array
  6.   'module' => string 'default' (length=7)
  7.   'action' => string 'index' (length=5)
  8.   'sf_culture' => string 'en' (length=2)
  9.   'year' => string '2009' (length=4)
  10.   'month' => string '08' (length=2)
  11.   'day' => string '20' (length=2)
  12.  
  13. // コンテキストの情報
  14. array
  15.   'path_info' => string '/' (length=1)
  16.   'prefix' => string '/frontend_dev.php' (length=17)
  17.   'method' => string 'GET' (length=3)
  18.   'format' => null
  19.   'host' => string 'sf-lab.fivestar.localhost' (length=26)
  20.   'is_secure' => boolean false
  21.   'request_uri' => string 'http://sf-lab.fivestar.localhost/frontend_dev.php' (length=50)

内部的に上記の3つの情報をシリアライズしてキーとしています。デフォルトの設定ではこのキーと値(URL)を単一の連想配列にセットして、1つの大きな ルーティング情報を保持している配列を作るということを行っています。この配列はシリアライズしてファイルに書き込まれ、 sfPatternRoutingが生成されるときに読み込まれてまた配列として定義される仕組みになっています。

問題はこの配列がどうして肥大化してしまったのかですが、キャッシュのキーにヒントがあります。実は今回開発していたものはモバイル用のサイトで、コンテ キストのrequest_uriにセッションIDがくっついてくる仕様になっており、異なるセッションIDでリクエストがくるたびにルーティングのキャッ シュを作り直すということが起きていました。セッションIDが変わった瞬間に作成していたルーティングのキャッシュが無意味になるどころか、無駄なルー ティングがものすごい勢いで増え続けるというかなり危険な状態ですね。

一応キャッシュには有効時間が設定されており、デフォルトでは31,556,926秒なのでおおよそ365日、つまり1年というまるであってないような状 態でもありました。これを短く設定するという方法もありましたが、そもそも定義してあるルーティングも大した量はなくそこまで時間がかかるわけでもなく、 アクションキャッシュなどでもある程度代替できるのでキャッシュを無効にして対応することにしました。

ルーティングのキャッシュを無効にする方法はアプリケーションのfactories.ymlで設定することができます。factories.ymlはコン トローラやリクエスト、ストレージなどのクラスの指定やクラスのコンストラクタに渡すパラメータなどを設定するファイルです。細かい設定などはThe Symfony Reference Bookをご確認ください。

このfactories.ymlを読み込むためのsfFactoryConfigHandlerというクラスがあり、このクラスを見てみると if (isset($parameters['cache'])) がtrueであればキャッシュオブジェクトを作成するということを行っています。ここで生成したキャッシュクラスがsfPatternRoutingに セットされることでキャッシュが有効になるという仕組みでした。デフォルトではsfFileCacheクラスがルーティングのキャッシュを行うクラスとし て設定されているようですが、次のように記述を行うことでキャッシュが無効になります。

  1. all:
  2.   routing:
  3.     class: sfPatternRouting
  4.     param:
  5.       # 他の設定
  6.       cache: ~

~(チルダ)はnullと同じ意味を持っているため、routingのparamという項目の中でcacheをnullにすると先ほどのissetがfalseになるため、キャッシュが無効に設定されます。
これでsymfony cache:clearコマンドを実行すると作られていたキャッシュも削除され、設定も反映されます。設定された後にメモリ使用量などを確認したところ正常な数値で稼働しているのが確認できました。


メモリの原因がルーティングのキャッシュにあることは当初全然見当もついておらず、ここまで内部を追っていくのは中々大変な作業でした。
メモリ使用量についてはWebデバッグツールバーには表示されますが本番環境で表示するわけにも行かず、memory_get_peak_usage関数などログに書き出すなどして場所の特定をしていきました。
特定するにあたって、まずはフロントコントローラ(web/index.phpなど)の処理をsfApplicationConfigurationオブ ジェクトの生成、sfContextの生成、dispatchの大きく3つにわけてメモリ使用量をみてみたところ、sfContextの生成時にメモリを 消費しているようです。

sfContextの生成はsfContext::createInstanceメソッドで行われており、さらに中を見ていくとfactories.ymlを読み込む処理でメモリを大量に消費しているというまで判明しました。
このとき少しはまったところがあり、sfContextは通常symfonyが入っているディレクトリのutilディレクトリ内にあるのですが、本番環境 ではsfContextなどのsymfonyのコアクラスをconfig_core_compile.yml.phpという名前でキャッシュされているた め、キャッシュファイルの方を修正する必要があります。

sfPatternRouingなども同様にconfig_core_compile.yml.phpに入っているため、このファイル内で徐々に処理を追っていった結果、最終的にルーティングのキャッシュに原因があるというところに到達することができました。
ちなみに開発環境では本番と同様にキャッシュクラス自体は生成されているのですが、デバッグモードを有効にしているため特にルーティングのキャッシュは行わないようになっています。


ルーティングのキャッシュはかなり盲点だったのですが、デフォルトで有効になっているためアプリケーションによっては今回のように知らず知らずのうちにメモリを大量に消費するという問題に陥ってしまうのではないかと感じました。
キャッシュの設定自体はファイルの他にもsfAPCCacheやsfMemcacheCacheなども利用できますし、ルーティングで lookup_cache_dedicated_keysというパラメータをtrueに設定すると複数のルーティングキャッシュを生成するようになるの で、アプリケーションによって適切な設定は変わってくると思います。

今回はルーティングのキャッシュについて知識がなかったので結果として悪い方向に働いてしまいましたが、チューニングの材料としてはとても有効だと思います。
symfonyは全体的にキャッシュに頼らないとパフォーマンスが出なかったりしますが、キャッシュを適切に設定してあげると目に見えて高速になるのでキャッシュは是非とも理解していきたいです。

コメント

    • おぉ。同じ問題にあってましたよ。

      http://forum.symfony-project.org/index.php/m/76842/#msg_num_8
      http://www.symfony-project.org/blog/2009/05/02/symfony-1-2-7-more-power

      にもあるように、Routing Cacheを使わないというのも一つの手ですね。後者のURLにあるように、一つのファイルに格納するのではなくて、たくさんのファイルにするってのもありですね。

      私もrouting cacheを使わない方法もやってみたことはみたのですが、いろいろ試しているうちに、とりあえずsfAPCCacheを使うことになりました。一日に一 度apc cacheをクリアしているので、イマイチな対処法ですが。。。やっぱりsfNoCacheの方がいいのかな。そこはまだ判断ができないです。

      あとsfMemcachedCacheを誰かしらんに勧められました。でも、このrouting cacheの問題はsymfony自体でなんとかしてもらいたいところですね。

引用元

更新:2010/01/06 20:19 カテゴリ: symfony  > チューニング ▲トップ
2件中 1 〜 2 表示  1 

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