カテゴリ:PHP

CakePHP パフォーマンスチューニング Pt.2

こんにちは、おがしょ〜です。
前回の更新から1ヶ月以上が経過してしまいました。。

さて前回、

もっと細かく調べてみたところ、CakePHPのコア部分でいくつか微妙な箇所がありました。

  1. モデルをたくさん使っているコントローラが遅い
  2. セッションのストア先にmemcachedを指定すると遅い
  3. ImageMagick(convert)の変換処理が遅い

と、3つの問題点を挙げていましたので、今回はそれについて適当なことを書いてみます。


まず1.の「モデルをたくさん使っているコントローラが遅い」について。
CakePHP1.2の便利な点として、割と使いやすいO/Rマッパーの存在があります。ざっくり言えばRDBをテーブル単位で構造化されたオブジェクト(モデルクラスとか)として扱える、ってそんな感じの機能です。さらにCakePHPでは、モデルとモデルの関連性を定義しておくことにより、O/Rマッパーが勝手に関連するものも取ってきてくれます。SQL的に言えばJOINするような感じで、これが超便利。


しかし、あまりに深い関連性を持たせるとなんでもかんでも取ってこられるので大変なことになります。
例えば、

ModelA =|1:n|= ModelB
ModelB =|1:n|= ModelC
ModelB =|1:n|= ModelD

と4つのモデルがこのような関係であった場合、ModelAのみ必要な場合に取得しようとすると、SQL的に “ModelA JOIN ModelB JOIN ModelC …” となってしまうとこは容易に想像つきますし、これは他の方も行っているexpectsメソッドを実装すれば割と簡単に解決できます。


でも問題はSQLクエリと結果の分量じゃなくて、CakePHPによって生成されるモデルインスタンス(と初期化時間)が問題でした。具体的には上記モデル構成の場合、リクエストのたびにModelA〜ModelDまでのインスタンスが必ず生成されます。expectsメソッドを実装したとしても、このインスタンス生成はコントローラの初期化時に行われるので全く効果がありません。


この辺りのCakePHPの動作をざっくりと・・・

  1. コントローラの初期化
  2. 使用するモデル定義を見る
  3. モデルインスタンスを作成
  4. 3.で作ったモデル内で、関連しているモデルインスタンスを作成
  5. 3〜4を関連するモデルすべてで行う。


という感じになっています。
4.の動作がとっても間抜けな感じで、作り手のモデル定義によってはシステム全体のモデルのインスタンスを生成することになってしまいます。


この問題の解決策としては、
  1. コントローラにモデルを登録せずに、メソッド内で必要なモデルのみを動的にアタッチする。(できるかどうか不明)
  2. モデルのインスタンス生成を実際に必要なときにのみ行う。


の2択で考えてましたが、
当時のプロジェクトの進行度的に、かなりの修正作業の入る選択肢1.は不可能でした。
で、2.を選びました。思いつきのキーポイントはisset()やget()など、PHPのマジックメソッド



要は関連するモデルの関連性のみを覚えておいて、実際にアプリがそのモデルを使おうとした時に、まだ無ければ初めてインスタンスを生成しよう、ってことです。適用前はコントローラの初期化時にかなりの数のインスタンスが生成されてましたが、適用後はメソッドごと必要な分のインスタンスのみが生成されているようです。


実際のコードについては以前CakePHP.jpのフォーラムに投げておいたのでそっちを参照してください。
(まったく反応がないのでちょっと寂しかったり 。・゚・(ノД`)・゚・。

ちなみに上記フォーラムに上げたコードにはその後に見つかった致命的なバグが残ってますが、お暇な方は試してみてください。

・・・とここまでを読み返してみて、やっぱり自分には文才が無いことを再確認しました。
長くなったので今日はこの辺で。

CakePHP パフォーマンスチューニング

こんにちは、おがしょ〜です。

現在進行中のプロジェクトでCakePHPなるフレームワークを使っているんですが、これが機能満載で思いのほか重い。使用しているバージョンは 1.2RC3 です。「CakePHP パフォーマンス」とかでググってみると1.1に比べてだいぶ遅いというベンチマーク結果などが出てきました。やっぱり。。


とはいえ開発も終盤でフレームワークの変更はしたくないですし、メインの開発陣とは別にチューニング箇所について調査することにしました。まずシステム構成ですが、

  • Linux(なんだっけ?)
  • Apache2
  • mysql5
  • mod_php5

まんまLAMPですね。
あとはアプリ側キャッシュにmemcachedを使ってたり、画像配信にSquidを前に立てたりします。


で、PHP&CakePHP側の主要な構成ですが、
  • コードキャッシュ&ローカルキャッシュにはAPCをチョイス。
  • CakeのCacheエンジンはAPCとMemcacheを用途に応じて切り替え。
  • Memcachedは複数サーバから同じデータを参照する必要のあるものにのみ使用する。それ以外のどうでもいいキャッシュはAPCに格納する。パフォーマンスはたぶんローカルmemcachedと同じようなものかと。
・・・な感じでしょうか。


まず、Apacheについてくる ab(ApacheBench) コマンドで実際のページのパフォーマンスを計測していきます。

>  $ ab http://example.jp/
コマンド終了時に表示された内容の「Requests per second: x.xx [#/sec]」の部分でだいたいの性能をみます。秒間n回リクエストを捌きました的な意味合いだと思います。


どんな数字が出れば速いのか?の基準については私もよくわかりません;;ただ体感的に考えて、1#/sec 程度だとブラウザの表示に最低1秒以上かかってることになるのであまりよろしくないですよね。この辺りの判断は人によります、たぶん・・・。


で、abで計測した結果をみて思ったことは、「なんか全体的に同じように遅い」でした。商品DBなんかの検索機能のあるページとかが飛び抜けて遅いだけなら複雑なSQLクエリとかが原因と考えられたりしますが、どうもそうではない様子。もっと細かく調べてみたところ、CakePHPのコア部分でいくつか微妙な箇所がありました。
  1. モデルをたくさん使っているコントローラが遅い
  2. セッションのストア先にmemcachedを指定すると遅い
  3. ImageMagick(convert)の変換処理が遅い


3はCakePHP関係ないですね;結論からいうと上記3つが主なボトルネックでして、解決することでかなりのパフォーマンスアップが図れました!が、長くなりそうなので解決編は次回以降のエントリに分けます・・・。