こんにちは、おがしょ〜です。
前回の更新から1ヶ月以上が経過してしまいました。。
さて前回、
もっと細かく調べてみたところ、CakePHPのコア部分でいくつか微妙な箇所がありました。
- モデルをたくさん使っているコントローラが遅い
- セッションのストア先にmemcachedを指定すると遅い
- 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の動作をざっくりと・・・
- コントローラの初期化
- 使用するモデル定義を見る
- モデルインスタンスを作成
- 3.で作ったモデル内で、関連しているモデルインスタンスを作成
- 3〜4を関連するモデルすべてで行う。
という感じになっています。
4.の動作がとっても間抜けな感じで、作り手のモデル定義によってはシステム全体のモデルのインスタンスを生成することになってしまいます。
この問題の解決策としては、
- コントローラにモデルを登録せずに、メソッド内で必要なモデルのみを動的にアタッチする。(できるかどうか不明)
- モデルのインスタンス生成を実際に必要なときにのみ行う。
の2択で考えてましたが、
当時のプロジェクトの進行度的に、かなりの修正作業の入る選択肢1.は不可能でした。
で、2.を選びました。思いつきのキーポイントはisset()やget()など、PHPのマジックメソッド。
要は関連するモデルの関連性のみを覚えておいて、実際にアプリがそのモデルを使おうとした時に、まだ無ければ初めてインスタンスを生成しよう、ってことです。適用前はコントローラの初期化時にかなりの数のインスタンスが生成されてましたが、適用後はメソッドごと必要な分のインスタンスのみが生成されているようです。
実際のコードについては以前CakePHP.jpのフォーラムに投げておいたのでそっちを参照してください。
(まったく反応がないのでちょっと寂しかったり 。・゚・(ノД`)・゚・。
ちなみに上記フォーラムに上げたコードにはその後に見つかった致命的なバグが残ってますが、お暇な方は試してみてください。
・・・とここまでを読み返してみて、やっぱり自分には文才が無いことを再確認しました。
長くなったので今日はこの辺で。