LAMP アプリケーションのパフォーマンス・チューニングを行う 5 つの簡単な方法

現在使用されている Web サーバー・アーキテクチャーのなかで、LAMP (Linux, Apache, MySQL, and PHP) アーキテクチャーは最も高い人気を集めている選択肢の 1 つです。この記事では、著者の John Mertic が、あらゆる LAMP アプリケーションでパフォーマンスを最適化するために実践するとよい 5 つの方法について詳しく説明します。

John Mertic, Software Engineer, SugarCRM

John Mertic は SugarCRM のソフトウェア・エンジニアであり、PHP による Web アプリケーションを数年間経験してきています。彼はこれまで SugarCRM で、データ統合や、モバイル・インターフェースやユーザー・インターフェースのアーキテクチャーなどを専門としてきました。彼は精力的なライターとして、php|architect や IBM developerWorks、Apple Developer Connector などに記事を発表しており、また『The Definitive Guide to SugarCRM: Better Business Applications』の著者でもあります。彼は多くのオープンソース・プロジェクトにも貢献しており、そのうち最も重要なものは PHP プロジェクトです。彼は PHP の Windows インストーラーの作成者でありメンテナーでもあります。



2011年 1月 25日

はじめに

Wikipedia、Facebook、Yahoo! などの代表的な Web サイトでは、LAMP アーキテクチャーを使用して 1 日数百万件ものリクエストに対応しています。さらに、Wordpress、Joomla、Drupal、SugarCRM などの Web アプリケーション・ソフトウェアでも、LAMP アーキテクチャーを使用することによって、組織が Web ベースのアプリケーションを簡単にデプロイできるようにしています。

このアーキテクチャーの長所は、その単純さにあります。.NET および Java™ 技術のようなスタックでは大規模なハードウェアと高価なソフトウェア・スタック、そして複雑なパフォーマンス・チューニングが使用されているかもしれませんが、LAMP スタックを実行するには、一般商品化されたハードウェアとオープンソースのソフトウェア・スタックがあれば十分です。けれども、使用されるソフトウェア・スタックは、1 つの大きなスタックではなく、緩やかに結び付けられたコンポーネントの集まりであることから、最適なパフォーマンスにチューニングするにはコンポーネントを 1 つひとつ分析してチューニングしなければなりません。そのため、パフォーマンス・チューニングは難しい作業になる可能性があります。

その一方で、あらゆる規模の Web サイトのパフォーマンスに大きな影響を与える、簡単なパフォーマンス・チューニングの方法がいくつかあります。この記事では、LAMP アプリケーションのパフォーマンスを最適化するための 5 つの方法について見て行きます。これらの方法では、アプリケーションのアーキテクチャーを変更する必要がほとんどないため、Web アプリケーションの応答性を最大にした上でハードウェアの要件を最大限に活かす確実かつ簡単な方法となります。


オペコード・キャッシュを利用すること

PHP アプリケーション (もちろん、LAMP の「P」の部分です) のパフォーマンスを向上させるのに最も簡単な方法は、オペコード・キャッシュを利用することです。私はどの Web サイトを扱う場合でも、必ずこのキャッシュが存在するようにしています。オペコード・キャッシュがないと、パフォーマンスにかなりの影響があるからです (多くの場合、オペコード・キャッシュが使用されていると、使用されていない場合に比べて応答時間が半分になります)。PHP に慣れていないほとんどの人々にとっては、なぜこれほどまでにパフォーマンスが向上するかが大きな疑問となると思いますが、その答えは、PHP が Web リクエストを処理する方法にあります。図 1 に、PHP リクエストの大まかなワークフローを示します。

図 1. PHP リクエスト
PHP リクエストのワークフローを示す図。ワークフローは、PHP スクリプトから始まって、構文解析、コンパイル、実行、そして最後の出力という流れになっています。

PHP は、C 言語や Java 言語のようなコンパイル言語ではなく、インタープリター言語なので、それぞれのリクエストに対して、構文解析・コンパイル・実行のステップ全体が行われます。この方法がいかに時間もリソースも消費することになるかは、おわかりのことと思います。リクエストが行われてから次のリクエストが行われるまでに、スクリプトが変更されることが稀な場合はなおさらのことです。構文解析されてコンパイルされた後のスクリプトは、一連のオペコードとして、マシンで構文解析可能な状態になります。ここで役に立つのが、オペコード・キャッシュです。オペコード・キャッシュは、すべてのリクエストで構文解析とコンパイルのステップを繰り返さなくても済むように、コンパイルされたスクリプトを一連のオペコードとしてキャッシュします。この場合、ワークフローは図 2 のようになります。

図 2. オペコード・キャッシュを利用した PHP リクエスト
スクリプトのオペコードがキャッシュにあるかどうかを調べ、存在する場合には構文解析とコンパイルのステップをスキップする論理フロー図。

つまり、PHP スクリプトのオペコードがキャッシュに存在する場合には、PHP リクエストを処理するプロセスで構文解析するステップと、コンパイルするステップをスキップして、直接キャッシュのオペコードを実行し、結果を出力します。このチェック・アルゴリズムは、スクリプト・ファイルを変更した場合にも対応します。従って、スクリプトが変更された後の最初のリクエストでは、オペコードが自動的に再コンパイルされて、以降のリクエストのためにキャッシュに入れられ、キャッシュ内の変更前のスクリプトに置き換わります。

PHP ではこれまで長い間、オペコード・キャッシュがよく使われてきました。初期のオペコード・キャッシュのなかには、PHP V4 の全盛期にまで遡るものもあります。現在、アクティブに開発が行われて使用されている人気のオペコード・キャッシュは以下の 3 つに絞り込まれます。

  • APC (Alternative PHP Cache) は、おそらく PHP で最も人気の高いオペコード・キャッシュです (「参考文献」を参照)。数名のコアとなる PHP 開発者たちによって開発された APC は、Facebook と Yahoo! のエンジニアたちの貢献によって速度と安定性が向上しました。さらに、この後取り上げるユーザー・キャッシュ・コンポーネントを含め、その他いくつかの点で PHP リクエストの処理速度が向上しています。
  • Wincache は、最も活発に開発が進められているオペコード・キャッシュです。これは、Microsoft® の IIS (Internet Information Services) チームが IIS Web サーバーを使用した Windows® 専用のオペコード・キャッシュとして開発しています (「参考文献」を参照)。Wincache が開発された主な目的は、Windows-IIS-PHP スタックでは APC が上手く機能しないことから、PHP をこのスタックでのファーストクラスの開発プラットフォームにすることでした。機能の点で APC と非常によく似た Wincache は、ユーザー・キャッシュ・コンポーネント、そして Wincache を直接セッション・ハンドラーとして利用する組み込みセッション・ハンドラーを誇っています。
  • eAccelerator は、PHP キャッシュの 1 つ、Turck MMCache オペコード・キャッシュから分岐したものです (「参考文献」を参照)。APC や Wincache とは異なり、eAccelerator は純粋なオペコード・キャッシュ兼オプティマイザーであり、ユーザー・キャッシュ・コンポーネントは含まれていません。UNIX® および Windows スタックに完全に対応した eAccelerator は、APC や Wincache が提供する追加機能を利用する予定のないサイトで非常によく使われています。つまり、memcache のようなソリューションによってマルチ Web サーバー環境に別個のユーザー・キャッシュ・サーバーを使用しようと計画している場合には、eAccelerator が選択されることがよくあります。

すべてのリクエストでスクリプトの構文解析とコンパイルを繰り返す必要をなくすオペコード・キャッシュは、間違いなく、PHP をスピードアップするための最初のステップです。この最初のステップが完了すれば、応答時間とサーバーのロードに改善が見られるはずですが、PHP を最適化する方法は他にもまだあります。次のセクションでは、他の方法を取り上げます。


PHP のセットアップを最適化すること

オペコード・キャッシュの実装によってパフォーマンスは飛躍的に向上しますが、その他にも、PHP セットアップを最適化するために行える微調整はたくさんあります。これらの微調整のベースとなるのは、php.ini ファイルの設定です。このファイルの設定は、開発またはテスト・インスタンスよりも本番インスタンスに適した設定となっています。設定を変更するとアプリケーションの問題をデバッグしにくくなる可能性があるため、変更しないほうがよい場合もあります。

ここからは、パフォーマンスを向上させるために重要ないくつかの設定オプションについて見て行きます。

無効に設定するとよいオプション

php.ini の設定のなかで、無効に設定したほうがよいオプションがいくつかあります。その理由は、大抵の場合、これらのオプションを指定する目的は後方互換性のためだからです。以下にそれらのオプションを挙げます。

  • register_globals — PHP V4.2 より前のバージョンでは、この機能がデフォルトで設定されており、着信リクエストの変数が自動的に通常の PHP 変数に割り当てられていました。この自動割り当てには重大なセキュリティー上の問題 (フィルタリングされていない着信リクエスト・データが通常の PHP 変数の内容と混ざり合ってしまうこと) があるだけでなく、すべてのリクエストでこの処理を行わなければならないことからオーバーヘッドが生じます。この機能を無効にすることで、アプリケーションの安全性は維持され、パフォーマンスも改善されます。
  • magic_quotes_* — これも PHP V4 の名残です。この機能により、着信データは危険を伴うフォーム・データを自動的にエスケープします。これは、着信データをサニタイジングしてからデータベースに送信するためのセキュリティー機能として設計されましたが、ユーザーをより一般的なタイプの SQL インジェクション攻撃から保護してくれるわけではなく、あまり効果的ではありません。ほとんどのデータベース層は、SQL インジェクション攻撃の危険に対し、遥かに効果的に対処する準備済みステートメントをサポートします。したがって、この機能も無効にして、迷惑なパフォーマンス問題を取り除いてください。
  • always_populate_raw_post_data — この機能が実際に必要になるのは、何らかの理由で、フィルタリングされていない着信 POST データのペイロード全体を調べなければならない場合に限られます。その必要がなければ、メモリーに POST データの重複したコピーを不要に蓄積するだけのことです。

ただし、以上のオプションをレガシー・コードで無効にするのは危険です。レガシー・コードは、これらのオプションが有効に設定されていなければ適切に実行されない可能性があります。新しいコードは、これらのオプションが有効に設定されているかどうかには依存しないように開発されているはずなので、既存のコードをリファクタリングして、可能な限り上記のオプションを使用しないようにする方法を探してください。

有効に設定するか、または設定の微調整が必要なオプション

php.ini ファイルには、有効に設定するとスクリプトの実行速度が増す、優れたパフォーマンス・オプションがあります。

  • output_buffering — これは必ず有効に設定してください。この機能が有効に設定されていると、echo 文や print 文ごとに処理結果が表示されるのではなく、出力が大きな塊でブラウザーにフラッシュされるからです。処理結果が文単位で表示される場合には、リクエストに対する応答が大幅に遅くなる可能性があります。
  • variables_order — このディレクティブは、着信リクエストに対する EGPCS (EnvironmentGetPostCookieServer) 変数の構文解析順序を設定します。特定のスーパーグローバル変数 (環境変数など) を使用していないのであれば、それらを削除することで、リクエストごとにスーパーグローバル変数を構文解析する必要がなくなり、多少のスピードアップにつながります。
  • date.timezone — これは、PHP V5.1 から追加されたディレクティブで、その当時導入された DateTime 関数で使用するデフォルト・タイムゾーンを設定するために使用されます。このディレクティブを php.ini ファイルで設定しないと、PHP はタイムゾーンが何であるかを突き止めるために数々のシステム・リクエストを行うことになります。PHP V5.3ではリクエストごとに警告が出されます。

上記の設定は、本番インスタンスで構成するとよい設定という点では、簡単に実現できる内容であると考えられます。PHP に関する限り、目を向けなければならない点はもう 1 つあります。それは、アプリケーションでの require()include() (および、それぞれの兄弟である require_once()include_once()) の使用です。すべてのリクエストでファイル・ステータスの確認を行うとなると、応答が遅くなる可能性があります。そこで、これらの関数を管理して不要なファイル・ステータスの確認が行われないようにすることで、PHP の構成およびコードを最適化することができます。


require()include() を管理すること

ファイル・ステータスの呼び出し (ファイルの存在を確認するために、ベースとなるファイルシステムに対して行う呼び出し) は、パフォーマンスという点でかなりのコストがかかる場合があります。ファイル・ステータスの確認に伴う大きな問題の 1 つは、コードをスクリプトに取り込むための require() 文および include() 文によってもたらされます。さらに、それよりも問題なのが、その兄弟関数の require_once()include_once() の呼び出しです。これらの関数は、ファイルの存在を確認するだけでなく、以前にファイルがインクルードされていないことも確認しなければなりません。

この問題に対処する最善の方法は何かと言うと、以下の対策を採って処理時間を短縮することです。

  • require() および include() の呼び出しには、常に絶対パスを使用してください。絶対パスを使用すれば、PHP にインクルードするファイルをより明確にすることができるため、ファイルの include_path 全体を調べる必要がなくなります。
  • include_path に含めるエントリーの数は少なく抑えてください。そうすることで、すべての require() および include() 呼び出しに絶対パスを指定することが難しい場合 (大規模なレガシー・アプリケーションでは、このような場合は珍しくありません)、インクルードするファイルが存在しないはずの場所を調べなくても済むため有効です。

APC と Wincache には、PHP によるファイル・ステータスの確認の結果をキャッシュするためのメカニズムがあるため、ファイルシステム・チェックを繰り返し行う必要はありません。このメカニズムは、インクルード・ファイルの名前が変数で制御されるのではなく、固定されている場合に特に効果的です。したがって、可能な限り、静的なインクルード・ファイル名を使用することが重要です。


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

データベースの最適化は、かなり高度なトピックになりがちです。記事にはこのトピックを完全に説明するだけのスペースはありませんが、速度という点でデータベースを最適化する方法を検討するとしたら、まず採るべき方法の数は限られます。それによって、最もよく発生する問題は回避できるはずです。

データベースはデータベース専用のマシンに配置すること

データベース・クエリーは、それだけでもかなりの処理量になり得ます。そこそこのサイズのデータ・セットで単純な SELECT 文を実行するだけで、CPU を 100 パーセント占有してしまうことも珍しくありません。Web サーバーとデータベース・サーバーが同じ 1 台のマシンで CPU 時間の取り合いをしているとしたら、リクエストの処理が完了するのが遅くなるのは確実です。したがって、最適化の最初のステップとしては、Web サーバーとデータベース・サーバーをそれぞれ別のマシンに配置し、データベース・サーバーにより多くのリソースを与えることが効果的だと思います (データベース・サーバーには大量のメモリーと複数の CPU が必要です)。

テーブルを適切に設計して索引を付けること

おそらく、データベースのパフォーマンスでの最大の問題は、データベースの不十分な設計と索引の欠如による結果としてもたらされています。典型的な Web アプリケーションで極めてよく実行される最も一般的なクエリーのタイプは、通常は SELECT 文です。SELECT 文は、データベース・サーバーで実行されるクエリーのうち、最も時間のかかるクエリーでもあります。その上、このような SQL 文は索引付けとデータベース設計が適切であるかどうかに最も影響されます。最適なパフォーマンスのヒントとしては、以下の指針を頼りにしてください。

  • 必ず各テーブルに主キーがあるようにしてください。主キーはテーブルのデフォルト順序を指定し、テーブルを他のテーブルに素早く結合する手段となります。
  • 必ずテーブル内の外部キー (あるテーブルのレコードを別のテーブルのレコードに結び付けるキー) に適切に索引が付けられるようにしてください。多くのデータベースでは、外部キーの値が実際に別のテーブルのレコードと一致するように、外部キーに対する制約が自動的に課せられるため、索引付けが適切にされるようになります。
  • テーブル内の列の数を制限するようにしてください。テーブル内にあまりにも多くの列があると、列が少ししかない場合よりも、クエリーのスキャン時間が大幅に長くなります。さらに、通常使用されない多数の列がテーブル内に含まれていると、NULL 値のフィールドでディスク・スペースを無駄にすることにもなります。このことは、テキストや BLOB など、可変サイズのフィールドにも当てはまります。可変サイズのフィールドが多く含まれていると、テーブルのサイズが必要以上に大きくなってしまう可能性があります。この場合、追加の列を別のテーブルに移し、レコードの主キーで列を結合するという方法を検討してください。

サーバー上で実行されているクエリーを分析すること

データベースのパフォーマンスを向上させる最善の手段は、どのクエリーがデータベース・サーバー上で実行されているか、そしてクエリーの実行にどれだけの時間がかかっているのかを分析することです。世間に出回っているほとんどすべてのデータベースには、このような分析ツールが用意されています。例えば MySQL では、問題のあるクエリーを見つけるために、スロー・クエリー・ログを利用することができます。このログを使用するには、MySQL 構成ファイルで slow_query_log を 1 に設定し、log_output を FILE に設定してください。すると、hostname-slow.log ファイルにスロー・クエリーが記録されるようになります。スロー・クエリーとみなすクエリーの実行時間 (秒数) は、long_query_time しきい値として設定することができます。最初はしきい値を 5 秒に設定し、その後、データ・セットに応じて徐々に 1 秒まで短縮していくことをお勧めします。このファイルには、リスト 1 のようなクエリーの詳細が記録されます。

リスト 1. MySQL のスロー・クエリー・ログ
/usr/local/mysql/bin/mysqld, Version: 5.1.49-log, started with:
Tcp port: 3306  Unix socket: /tmp/mysql.sock
Time                 Id Command    Argument
# Time: 030207 15:03:33
# User@Host: user[user] @ localhost.localdomain [127.0.0.1]
# Query_time: 13  Lock_time: 0  Rows_sent: 117  Rows_examined: 234
use sugarcrm;
select * from accounts inner join leads on accounts.id = leads.account_id;

このログで注目する必要がある重要な項目は、クエリーの所要時間を示す Query_time です。また、Rows_sentRows_examined の数値にも注目してください。これらの数値が示す、調べた行数や返された行数が多すぎる場合、それはクエリーが不適切に作成されている可能性を示唆します。クエリーがどのように作成されているかについて深く掘り下げるには、クエリーの先頭に EXPLAIN を追加してください。すると、結果セットの代わりにクエリー・プランが返されます (リスト 2 を参照)。

リスト 2. MySQL で EXPLAIN を使用した結果
mysql> explain select * from accounts inner join leads on accounts.id = leads.account_id;
+----+-------------+----------+--------+--------------------------+---------+---
| id | select_type | table    | type   | possible_keys           
 | key     | key_len | ref                       | rows | Extra |
+----+-------------+----------+--------+--------------------------+---------+--------
|  1 | SIMPLE      | leads    | ALL    | idx_leads_acct_del       | NULL    | NULL    
| NULL                      |  200 |       |
|  1 | SIMPLE      | accounts | eq_ref | PRIMARY,idx_accnt_id_del | PRIMARY | 108    
| sugarcrm.leads.account_id |    1 |       |
+----+-------------+----------+--------+--------------------------+---------+---------
2 rows in set (0.00 sec)

EXPLAIN を指定した場合の出力内容については MySQL のマニュアルで詳しく説明していますが (「参考文献」を参照)、私がこの出力で最も注目するのは、「type」列が「ALL」になっている箇所です。これは、MySQL がテーブル全体をフル・スキャンしなければならないことを意味します。つまり、キーを使用しないで検索するということです。このような情報が、索引を追加するとクエリーの実行時間が大幅に短縮される箇所を見つけるのに役立ちます。


データを効果的にキャッシュに入れること

前のセクションからおわかりのように、Web アプリケーションのパフォーマンスにとって最大のネックとなりがちなのがデータベースです。その一方、クエリー対象のデータが頻繁に変更されないとしたらどうでしょうか。その場合、リクエストを実行する度にクエリーを呼び出すのではなく、クエリーの実行結果をローカルに保管しておくほうが有効な手段となる可能性があります。

前に説明したオペコード・キャッシュのうち、APC と Wincache の 2 つには、クエリーの実行結果をローカルに保管する機能が備わっています。そのため、PHP データを直接共有メモリー・セグメントに保管して、素早く取得することができます。リスト 3 に、この方法の一例を記載します。

リスト 3. APC を使用してデータベースにクエリーを実行した結果をキャッシュする例
<?php

function getListOfUsers()
{
    $list = apc_fetch('getListOfUsers');
    
    if ( empty($list) ) {
        $conn = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'dbuser', 'dbpass');
        $sql = 'SELECT id, name FROM users ORDER BY name';
        foreach ($conn->query($sql) as $row) {
            $list[] = $row;
        }
        
        apc_store('getListOfUsers',$list);
    }
    
    return $list;
}

上記の場合、クエリーを 1 度行うだけで済みます。クエリーを行った後、その結果を getListOfUsers というキーで APC ユーザー・キャッシュに格納します。それ以降はキャッシュの有効期限が切れるまで、SQL クエリーを実行せずに、結果配列をキャッシュから直接取得することができます。

ユーザー・キャッシュとして使用できるのは、APC と Wincache だけではありません。memcache と Redis もよく使用されており、この 2 つは Web サーバーと同じサーバー上でユーザー・キャッシュを実行する必要はありません。Web アプリケーションが複数の Web サーバーにスケールアウトされる場合はなおさらのこと、memcache と Redis はさらにパフォーマンスと柔軟性を向上させることができます。


まとめ

この記事では、LAMP アプリケーションのパフォーマンスを向上させる 5 つの単純な方法を見てきました。これらの方法は、PHP レベルで検討しただけではありません。オペコード・キャッシュを使用し、さらに PHP 構成を最適化するという方法の他に、データベース設計を最適化して適切な索引を付ける方法も説明しました。また、ユーザー・キャッシュを利用することにも目を向け、データが頻繁に変更されない場合にはデータベース呼び出しを繰り返さなくても済むようにする方法を説明しました。


ダウンロード

内容ファイル名サイズ
Source codeos-5waystunelamp.zip---

参考文献

学ぶために

製品や技術を入手するために

  • APC (Alternative PHP Cache) は、PHP でおそらく最もよく使われているオペコード・キャッシュです。
  • Wincache は最も活発に開発が進められているオペコード・キャッシュです。これは、Microsoft® の IIS (Internet Information Services) チームが IIS Web サーバーを使用した Windows® 専用のオペコード・キャッシュとして開発しています。
  • eAccelerator は PHP キャッシュの 1 つ、Turck MMCache オペコード・キャッシュから分岐したものです。
  • IBM ソフトウェアの試用版を使用して、次のオープンソース開発プロジェクトを革新してください。ダウンロード、あるいは DVD で入手できます。
  • IBM 製品の評価版をダウンロードするか、あるいは IBM SOA Sandbox のオンライン試用版で、DB2®、Lotus®、Rational®、Tivoli®、および WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Linux
ArticleID=627559
ArticleTitle=LAMP アプリケーションのパフォーマンス・チューニングを行う 5 つの簡単な方法
publish-date=01252011