This project is no longer maintained and has been archived. |
はじめに
大きなアプリケーションにとってパフォーマンスはとても重要な側面です。Doctrineはオブジェクトリレーショナルマッピングと同様に大きなデータベース抽象化レイヤーを提供する抽象化ライブラリです。これがポータビリティと開発のしやすさを提供する一方でパフォーマンスの観点から欠点になるのは避けられません。この章では読者がDoctrineのベストなパフォーマンスを得られるようにするためのお手伝いを試みます。
コンパイル
Doctrineはとても大きなフレームワークなのでそれぞれのリクエストごとにたくさんのファイルがインクルードされます。これは大きなオーバーヘッドをもたらします。実際これらのファイルオペレーションはデータベースに複数のクエリを送信するのと同じくらい時間がかかります。開発環境ではファイルごとにクラスを明確に分離する方法はうまくゆきますが、プロジェクトが商用ディストリビューションになるとき動作速度がファイルごとのクラス分離の原則よりも優先されます。
Doctrineはこの問題を解決するためにcompile()
と呼ばれるメソッドを提供します。compileメソッドは最もよく使われるDoctrineコンポーネントの単独のファイルを作成します。デフォルトではこのファイルはDoctrine.compiled.php
という名前でDoctrineのrootに作成されます。
コンパイルは複数のファイル(最悪の場合数十のファイル)の代わりにコンパイルされたファイルを含む最も使われるDoctrineのランタイムコンポーネントの単独ファイルを作成するための手段で桁違いのパフォーマンスを改善できます。失敗ケースでは、Doctrine_Exception
が詳細なエラーを投げます。
Doctrineのコンパイルを処理するcompile.php
という名前のコンパイルスクリプトを作りましょう:
// compile.php
require_once('/path/to/doctrine/lib/Doctrine.php'); spl_autoload_register(array('Doctrine', 'autoload'));
Doctrine_Core::compile('Doctrine.compiled.php');
compile.php
を実行するとDoctrine.compiled.php
ファイルがdoctrine_test
フォルダのrootに生成されます:
$ php compile.php
使用しているデータベースドライバのみをコンパイルしたいのであればcompile()
に2番目の引数としてドライバの配列を渡すことができます。この例ではMySQLのみを使用しているのであればDoctrineにmysql
ドライバのみをコンパイルするように伝えましょう:
// compile.php
// ...
Doctrine_Core::compile('Doctrine.compiled.php', array('mysql'));
コンパイルされたDoctrineを含めるためにbootstrap.php
スクリプトを変更できます:
// bootstrap.php
// ...
require_once('Doctrine.compiled.php');
// ...
コンサーバティブな取得
おそらく最も重要なルールはコンサーバティブなモードで実際に必要なデータのみを取得することです。ささいなことに聞こえるかもしれませんができることに対する無精や知識の欠如は必要のないオーバーヘッドにつながることがよくあります。
例を見てみましょう:
$record =
table->find(
id);
このようにコードを書くのはどのくらいの頻度でしょうか?便利ですが望むものではありません。上記の例はデータベースからレコードのすべてのカラムを引き出し新しく作成されたオブジェクトにこのデータを投入します。これは不必要なネットワークトラフィックだけでなくDoctrineデータを決して使われないオブジェクトに投入しなければならないことも意味します。
次のようなクエリが理想的ではない理由は読者のみなさんはみんなご存知だと思います:
SELECT * FROM my_table
上記のコードはどのアプリケーションでも悪いものでDoctrineを使う際にも当てはまります。オブジェクトに必要のないデータを投入するのは時間の無駄使いになるのでDoctrineを使うとより悪くなります。
このカテゴリに所属する別の重要なルールは: 本当に必要なときだけオブジェクトを取得することです。Doctrineはオブジェクトグラフの代わりに"配列グラフ"を取得する機能を持ちます。最初は奇妙なことに聞こえるかもしれません。ではなぜオブジェクトリレーショナルマッパーを使うのかもう一度考えてみましょう。PHPは優れたOOPのために多くの機能で強化された手続き型の言語です。それでも配列はPHPで使うことのできる最も効率的なデータ構造です。複雑なビジネスロジックを実現する際にオブジェクトは最も価値があります。データをオブジェクト構造にラッピングしても恩恵がない場合はリソースの無駄使いです。記事用の関連データを持つすべてのコメントを取得する次のコードを見てみましょう。後で表示するためにこれらをビューに渡します:
$q = Doctrine_Query::create() ->select('b.title, b.author,
b.created_at') ->addSelect('COUNT(t.id) as num_comments') ->from('BlogPost b') ->leftJoin('b.Comments c') ->where('b.id = ?') ->orderBy('b.created_at DESC');
$blogPosts = $q->execute(array(1));
最新のblog投稿をレンダリングするビューもしくはテンプレートを想像してください:
-
Posted on by .
- )
ビューの中で配列の代わりにオブジェクトを使う利点を想像できますか?ビューの中でビジネスロジックを実行しているわけではないですよね?1つのパラメータによってたくさんの不必要な処理をしなくて済みます:
// ...
$blogPosts = $q->execute(array(1), Doctrine_Core::HYDRATE_ARRAY);
望むのであればsetHydrationMethod()
メソッドを使うこともできます:
// ...
$q->setHydrationMode(Doctrine_Core::HYDRATE_ARRAY);
$blogPosts = $q->execute(array(1));
上記のコードはデータをオブジェクトではなくはるかに負荷が低い配列にハイドレイトします。
NOTE 配列のハイドレーションに関して1つの素晴らしいことはオブジェクトで
ArrayAccess
を使用する場合配列のハイドレーションを使用するようにクエリを切り替えばコードはまったく同じように動作します。例えば最新のblog投稿をレンダリングするために書いた上記のコードはその背後のクエリを配列のハイドレーションに切り替えるときに動作します。
ときに、オブジェクトや配列ではなくPDOから直接出力したいことがあります。これを行うには、ハイドレーションモードを ``Doctrine\_Core::HYDRATE_NONE``に切り替えます。例は次の通りです:
$q = Doctrine_Query::create() ->select('SUM(d.amount)')
->from('Donation d');
$results = $q->execute(array(), Doctrine_Core::HYDRATE_NONE);
結果を出力するとDQLクエリに依存する配列形式の値が見つかります:
print_r($results);
この例では結果は次のコードでアクセスできます:
$total = $results[0][1];
クラスファイルをバンドルする
Doctrineもしくは他の大きなOOライブラリもしくはフレームワークを使うとき通常のHTTPリクエストでインクルードされるファイルの数は秘女に大きくなります。1つのリクエストで50-100のファイルがインクルードされるのも珍しいことではありません。これはたくさんのディスクオペレーションにつながるのでパフォーマンスに大きな影響を及ぼします。これは一般的に開発環境では問題ありませんが、本番環境には適していません。この問題に対処するための推奨方法はもっともよく使われるライブラリのクラスを本番環境用に1つのファイルにまとめ、不要なホワイトスペース、改行とコメントを剥ぎ取ります。この方法によってバイトコードキャッシュ無しでも大きなパフォーマンスの改善ができます(次のセクションをご覧ください。このようなバンドルを作成するベストな方法は自動化ビルド処理、例えばPhingによる方法です。
バイトコードキャッシュを使用する
APCのようなバイトコードキャッシュは実行に先駆けてPHPによって生成されます。このことはファイルの解析とバイトコードの作成は毎回のリクエストごとではなく一度だけ行われることを意味します。これはとりわけ大きなライブラリ/フレームワークを利用しているときに役立ちます。本番環境用のファイルを利用することで大きなパフォーマンスの改善ができます。キャッシュを最適化するための設定オプションがたくさんあるのでバイトコードを最大限活用するにはマニュアルページを調べる必要があります。
オブジェクトを開放する
バージョン5.2.5に関して、PHPは循環参照を持つオブジェクトグラフのガーベッジコレクションを行うことができません。例えば親が子への参照を持ち子が親に参照を持つ場合です。多くのDoctrineオブジェクトモデルがこのようなリレーションを持つので、オブジェクトがスコープの外側にゆくときでさえPHPはメモリーを解放しません。
大抵のPHPアプリケーションに関して、この問題はほとんど関係しません。PHPスクリプトの実行時間が短い傾向にあるからです。長い実行時間のスクリプト、例えば、バルクデータインポーターやエクスポーターなどの実行時間の長いスクリプトは循環参照のチェーンを破棄しない限りメモリを使い果たしてしまうことがあります。DoctrineはDoctrine\_Record
、Doctrine\_Collection
、とDoctrine_Query
でfree()
メソッドを提供します。このメソッドはこれらのオブジェクト上の循環参照を削除します。ガベージコレクション用にこれらを開放します。使い方は次の通りです:
大量のレコードを挿入するときにオブジェクトを解放します:
for ($i = 0; $i < 1000; $i++) { $object = createBigObject();
$object->save(); $object->free(true); }
同じ方法でクエリオブジェクトを解放することもできます:
for ($i = 0; $i < 1000; $i++) { $q = Doctrine_Query::create()
->from('User u');
$results = $q->fetchArray(); $q->free(); }
もしくはループの中のそれぞれのクエリに対して同じクエリオブジェクトを使う場合よりベターです:
$q = Doctrine_Query::create() ->from('User u');
for ($i = 0; $i < 1000; $i++) { $results = $q->fetchArray(); $q->free(); }
他のティップス
DQLパーサーを手助けする
DQLを使用する際に可能な方法は2つあります。最初の方法はプレーンなDQLクエリを書きこれらをDoctrine\_Connection::query($dql)
に渡すことです。2番目の方法はDoctrine\_Query
オブジェクトと流れるようなインターフェイスを使うことです。とてもシンプルなクエリ以外は後者の方が望ましいです。これはDoctrine_Query
オブジェクトとそのメソッドを利用することでDQLパーサーの負担が少し減るからです。これは解析する必要のあるクエリの量を減らすのでそれゆえ速くなります。
効率的なリレーションのハンドリング
2つのコンポーネントの間のリレーションを追加したい場合次のようなことを行うべきではありません:
NOTE 次の例は
Role
とUser
の多対多のリレーションを想定します。
$role = new Role(); $role->name = 'New Role Name';
$user->Roles[] = $newRole;
CAUTION 上記のコードはロールがまだロードされていない場合データベースからすべてのロールをロードします!1つの新しいリンクを追加するためだけに!
代わりに次の方法が推奨されます:
$userRole = new UserRole(); $userRole->role_id = $role_id;
$userRole->user_id = $user_id; $userRole->save();
まとめ
Doctrineのパフォーマンスを改善するメソッドはたくさん存在します。この章で説明されたメソッドを検討することを多いに推奨します。
Doctrineで使われている[doc technology テクノロジー]を学ぶために次の章に移動します。