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

あなたのウェブアプリケーションのパフォーマンスに影響を及ぼす要因は数多くあります。 環境の要因もありますし、あなたのコードに関係する要因もあります。 また、Yii そのものに関係する要因もあります。 この節では要因のほとんどを列挙して、どのようにそれらを修正すればあなたのアプリケーションのパフォーマンスを向上させることが出来るかを説明します。

PHP 環境を最適化する

PHP 環境を正しく構成することは非常に重要です。 最大のパフォーマンスを得るためには、

  • 最新の安定した PHP バージョンを使うこと。 使用する PHP のメジャーリリースを上げると、顕著なパフォーマンスの改善がもたらされることがあります。
  • Opcache (PHP 5.5 以降) または APC (PHP 5.4 以前) を使って、バイトコードキャッシュを有効にすること。 バイトコードキャッシュによって、リクエストが入ってくるたびに PHP スクリプトを解析してインクルードする時間の浪費を避けることが出来ます。

デバッグモードを無効にする

本番環境でアプリケーションを実行するときには、デバッグモードを無効にしなければなりません。 Yii は、YII_DEBUG という名前の定数の値を使って、デバッグモードを有効にすべきか否かを示します。 デバッグモードが有効になっているときは、Yii はデバッグ情報の生成と記録のために時間を余計に費やします。

エントリスクリプト の冒頭に次のコード行を置くことによってデバッグモードを無効にすることが出来ます。

defined('YII_DEBUG') or define('YII_DEBUG', false);

Info|情報: YII_DEBUG のデフォルト値は false です。 従って、アプリケーションコードの他のどこかでこのデフォルト値を変更していないと確信できるなら、単に上記の行を削除してデバッグモードを無効にしても構いません。

キャッシュのテクニックを使う

さまざまなキャッシュのテクニックを使うと、あなたのアプリケーションのパフォーマンスを目に見えて改善することが出来ます。 たとえば、あなたのアプリケーションが Markdown 形式のテキスト入力をユーザに許可している場合、解析済みの Markdown のコンテントをキャッシュすることを考慮してください。 そうすれば、リクエストごとに毎回同じ Markdown テキストの解析を繰り返すことを回避できるでしょう。 Yii によって提供されているキャッシュのサポートについて学ぶためには キャッシュ の節を参照してください。

スキーマキャッシュを有効にする

スキーマキャッシュは、アクティブレコード を使おうとする場合には、いつでも有効にすべき特別なキャッシュ機能です。 ご存じのように、アクティブレコードは、賢いことに、あなたがわざわざ記述しなくても、DB テーブルに関するスキーマ情報 (カラムの名前、カラムのタイプ、外部キー制約など) を自動的に検出します。 アクティブレコードはこの情報を取得するために追加の SQL クエリを実行しています。 スキーマキャッシュを有効にすると、取得されたスキーマ情報はキャッシュに保存されて将来のクエリで再利用されるようになります。

スキーマキャッシュを有効にするためには、アプリケーションの構成情報 の中で、cache アプリケーションコンポーネント にスキーマ情報を保存するように構成し、[[yii\db\Connection::enableSchemaCache]] を true に設定します。

return [
    // ...
    'components' => [
        // ...
        'cache' => [
            'class' => 'yii\caching\FileCache',
        ],
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=mydatabase',
            'username' => 'root',
            'password' => '',
            'enableSchemaCache' => true,

            // スキーマキャッシュの持続時間
            'schemaCacheDuration' => 3600,

            // スキーマ情報を保存するのし使用されるキャッシュコンポーネントの名前
            'schemaCache' => 'cache',
        ],
    ],
];

アセットを結合して最小化する

複雑なウェブページでは、多数の CSS や JavaScript のアセットファイルをインクルードすることがよくあります。 HTTP リクエストの回数、および、これらのアセットの全体としてのダウンロードサイズを削減するために、アセットを単一のファイルに結合して、それを圧縮することを考慮すべきです。 これによって、ページのロードにかかる時間とサーバの負荷を大きく削減することが出来ます。 詳細については、アセット の節を参照してください。

セッションのストレージを最適化する

デフォルトでは、セッションのデータはファイルに保存されます。 開発と小さなプロジェクトではそれでも構いません。 しかし、大量のリクエストを並列処理するとなると、データベースのような、もっと洗練されたストレージを使う方が良いでしょう。 Yii はさまざまなセッションストレージのサポートを内蔵しています。 これらのストレージは、アプリケーションの構成情報 の中で session コンポーネントを次のように構成することによって使用することが出来ます。

return [
    // ...
    'components' => [
        'session' => [
            'class' => 'yii\web\DbSession',

            // デフォルトの 'db' 以外の DB コンポーネントを使用したい場合は
            // 以下を設定する
            // 'db' => 'mydb',

            // デフォルトの session テーブルをオーバーライドするためには
            // 以下を設定する
            // 'sessionTable' => 'my_session',
        ],
    ],
];

上記の構成は、セッションデータの保存にデータベーステーブルを使用するものです。 デフォルトでは、db アプリケーションコンポーネントをデータベース接続として使用し、セッションデータを session テーブルに保存します。 ただし、前もって session テーブルを次のように作っておく必要があります。

CREATE TABLE session (
    id CHAR(40) NOT NULL PRIMARY KEY,
    expire INTEGER,
    data BLOB
)

[[yii\web\CacheSession]] を使って、セッションをキャッシュに保存することも出来ます。 理論上、サポートされている キャッシュストレージ のどれでも使うことが出来ます。 ただし、キャッシュストレージの中には、容量の上限に達したときにキャッシュされたデータをフラッシュするものがあることに注意してください。 この理由により、主として容量の上限が無い種類のキャッシュストレージを使用すべきです。

あなたのサーバに Redis がある場合は、[[yii\redis\Session]] によって redis をセッションストレージとして使用することを強く推奨します。

データベースを最適化する

DB クエリの実行とデータベースからのデータ取得がウェブアプリケーションのパフォーマンスの主たるボトルネックになることがよくあります。 データキャッシュ の使用によってパフォーマンスの劣化を緩和することは出来ますが、問題を完全に解決することは出来ません。 データベースが膨大なデータを抱えている場合、キャッシュされたデータが無効化されたときに最新のデータを取得するためのコストは、データベースとクエリが適切に設計されていないと、法外なものになり得ます。

DB クエリのパフォーマンスを向上させるための一般的なテクニックは、フィルタの対象になるテーブルカラムにインデックスを作成することです。 例えば、username によってユーザのレコードを検索する必要があるなら、username に対してインデックスを作成するべきです。 ただし、インデックスを付けると SELECT クエリを非常に速くすることが出来る代りに、INSERT、UPDATE、または DELTE のクエリが遅くなることに注意してください。

最後にもう一つ大事なことですが、SELECT クエリで LIMIT を使ってください。 こうすることで、大量のデータが返されて、PHP のために確保されたメモリを使い尽くすということがなくなります。

プレーンな配列を使う

アクティブレコード は非常に使い勝手のよいものですが、データベースから大量のデータを取得する必要がある場合は、プレーンな配列を使うほどには効率的ではありません。 そういう場合は、アクティブレコードを使ってデータを取得する際に asArray() を呼んで、取得したデータがかさばるアクティブレコードのオブジェクトではなく配列として表現されるようにすることを考慮するのが良いでしょう。 例えば、

class PostController extends Controller
{
    public function actionIndex()
    {
        $posts = Post::find()->limit(100)->asArray()->all();

        return $this->render('index', ['posts' => $posts]);
    }
}

上記において、$posts は、テーブル行の配列としてデータを代入されることになります。 各行はプレーンな配列になります。 $i 番目の行の title カラムにアクセスするためには、$posts[$i]['title'] という式を使うことが出来ます。

クエリを構築するのに DAO を使って、データをプレーンな配列に取得することも出来ます。

Composer オートローダを最適化する

Composer のオートローダは、ほとんどのサードパーティのクラスファイルをインクルードするのに使われますので、次のコマンドを実行して Composer のオートローダを最適化することを考慮すべきです。

composer dumpautoload -o

オフラインでデータを処理する

リクエストが何らかのリソース集約的な操作を必要とするものである場合は、そういう操作が終るまでユーザを待たせずに、オフラインモードで操作を実行する方策を考えるべきです。

オフラインでデータを処理するための方法が二つあります。 すなわち、プルとプッシュです。

プルの方法では、リクエストが何らかの複雑な操作を必要とするたびに、タスクを作成してデータベースなどの持続的ストレージに保存します。 そうしておいて、別の独立したプロセス (例えばクロンジョブ) を使い、タスクを引き出して処理します。 この方法は、実装は容易ですが、いくつかの欠点があります。 例えば、タスクのプロセスはストレージから定期的にタスクを引き出さなければなりません。 引き出す間隔が長すぎると、タスクの処理に大きな遅延が生じます。しかし、間隔が短すぎると、オーバーヘッドが大きくなります。

プッシュの方法では、タスクを管理するのにメッセージキュー (例えば、RabbitMQ、ActiveMQ、Amazon SQS など) を使用します。 新しいタスクがキューに入れられるたびに、タスクを処理するプロセスが起動されたり通知を受けたりして、タスク処理がトリガされます。

パフォーマンスプロファイリング

あなたは、あなたのコードをプロファイルして、パフォーマンスのボトルネックを発見し、それに応じた適切な手段を講じるべきです。 次のプロファイリングツールが役に立つでしょう。