なぜ、 IIS は PHP アプリケーションの実行に不向きとされてきたのか?
震災の影響と、業務が立て込んでいたために久々のブログの投稿となります。
あらためて、今回の震災で被災された皆様には、心よりお見舞い申し上げます。
私の実家も福島にありますが、会津地方ということで大きな被害はありませんでしたが、友人や同僚の実家などでは被害にあったところもあり、なかなか精神的にこたえるものがありました。
原発の問題など、まだまだ厳しい状況が続きますが、復興と再生を信じて頑張っていきましょう!
ということで (←なにが?) 、今回は、長い間囁かれてきたものの、その詳細についてはあまり語られることのなかった IIS と PHP にまつわる噂、「IIS で PHP を動作させるとパフォーマンスが悪い、安定しない」という件について、事実なのか否か、そしてそれはいかなる理由によるものなのか、掘り下げて書いてみたいと思います。
なぜ、 IIS は PHP アプリケーションの実行に不向きとされてきたのか?
IIS はリリース当時から PHP を CGI ( Common Gateway Interface ) として ホストすることができました。
PHP 4 からは PHP ランタイムがマルチスレッド対応となり、IIS 用として ISAPI (Internet Server Application Programming Interface) モジュールも提供され、IIS 上でも効率よく高速に動作することが可能になりました。
また、XAMP が登場する以前の PHP の入門書をひも解いてみると、その多くで IIS 上で PHP を動作させるための設定が紹介されていることは、その当時に書籍で PHP を学習されたことのある方であればご存じのことでしょう。
しかし長い間、PHP ユーザーの間からは「IIS で PHP を動作させるとパフォーマンスが悪い、安定しない」等々言われてきました。
これはなぜでしょう?
この問題の背景には、IIS のアプリケーションを実行する方式と、PHP ランタイムを取り巻く環境が複雑に絡み合っていたのです。
IIS における PHP アプリケーションの実行方式
IIS 7.0 がリリースされる以前の IIS では、アプリケーション をホストする方式として、CGI と ISAPI という 2 つの方式を選択することができました。
CGI 方式によるアプリケーションのホストでは、①Web サーバー ( IIS ) は、スクリプト ファイルへのリクエストを受けると、スクリプトを処理するためのプログラムを起動します。②起動されたプログラムはスクリプトを実行し、結果を標準出力に送ると終了します。③IIS は出力された結果を読み取りクライアントにレスポンスします。
この一連の動作はすべてのリクエストに対して行われます。たとえば、100 個のリクエストを同時に受けると、 100 個のプログラムが起動することになります。
この方式は、リクエストごとに個別のプロセス空間を使用するためスケーラビリティ的にも優れないことはもちろん、プロセスの起動とシャットダウンにかかるオーバーヘッドが大きいため、Web アプリケーションをホストする方法として効率的ではありません。
いっぽう、もう一つのアプリケーションのホスト方式である ISAPI モジュールによるアプリケーションのホストでは、リクエストの処理はアプリケーション プロセスではなく、サーバー プロセス上のスレッドで行われます。そのため、処理にかかるオーバーヘッドが少なく、高速に動作することができ、スケーラビリティにも優れています。
ISAPI とは IIS が提供している API です。ISAPI を利用して作成された アプリケーションは、IIS のネイティブ モジュールとして組み込むことができ、IIS の一部として動作することができます。
そのためマイクロソフトの Web アプリケーションのエンジンである ASP.NET をはじめ、IIS 上での動作を想定している多くの アプリケーションのランタイムでは、実行エンジンとして ISAPI モジュールを提供しています。
PHP ランタイムの IIS 対応と問題
PHP ランタイムは、PHP 3 までは、マルチスレッドには対応しておらず、ISAPI のモジュールも提供されていなかったため IIS で PHP をホストするには、CGI としてホストする必要がありました。そのため IIS 上では、IIS、PHP 双方ともに本来のパフォーマンスを十分に引き出すことはできませんでした。
PHP 4 になり、マルチスレッド対応となった PHP ランタイムには、Thread Safe な ISAPI モジュールが追加され、IIS 上で効率よくホストすることが可能になりました。
実際のところ PHP4.0 の ISAPI モジュールは IIS 上で問題なく、快適に動作しました。
しかし、この頃から「 PHP を IIS 上で動かすと安定しない」という声が聞かれるようになりました。
これはなぜでしょう?
原因は IIS でも PHP ランタイムでもなく、多くは、追加可能な PHP の拡張モジュールにありました。
マルチスレッドで動作する ISAPI モジュールはその性質上、Thread Safe ( スレッドセーフ ) 、つまり同じプログラム コードが複数のスレッドで同時並行的に実行しても問題が発生しないことを保証するもでなければなりませんでした。
PHP 4 の提供する ISAPI モジュールは Thread Safe に作られていましたが、様々なコミニュティから提供されている PHP の拡張モジュールの中には Thread Safe でないものも多数存在していました。
Thread Safe でない PHP 拡張モジュールは、マルチスレッドで動作する PHP 4 ランタイムから正常に使用することはできません。
Thread Safe でないプログラムでは、スレッドの同期や、スレッド間の通信なども考慮されていませんので、使用しているうちに様々な問題が発生します。
こういった情報はあまり知られておらず、ユーザーが気づかずに ISAPI で動作する PHP ランタイムに、Thread Safe でない拡張モジュールを追加してしまい、使用しているうちに問題が発生するということがよくおこりました。
このような問題の回避策として、IIS 上の PHP を CGI でホストするという方法も採られるようになりました。しかし、前述のとおり CGI よる ホストはパフォーマンスを含め多くの面で効率的でなく、期待するようなパフォーマンス得ることはできませんでした。
とくに Windows OS は、UNIX 系の OS とは、プロセス マネージメントの設計思想が異なり、新規のプロセス生成にはコストがかかるため、同じハードウェア スペックで CGI の実行速度の比較した場合に Windows OS の方がパフォーマンス的に劣るということもありました。
かくして「IIS で PHP を動作させるとパフォーマンスが悪い、安定しない」、総じて 「 IIS は PHP の実行に向いていない」と言われるようになってしまったのです。
Apache 1.x における PHP の動作
では Apache ではどうだったのでしょう? PHP ランタイムには、PHP/FI 時代から Apache HTTP サーバー用として mod_php というサーバーモジュールを提供していました。
mod_php では問題は発生しなかったのでしょうか? また、それはなぜ発生しなかったのでしょう?
それには Apache HTTP サーバーのリクエスト処理の動作が関係しています。
Apache 1.x はリクエストの処理をスレッドでは行わず、自分自身のプロセスをコピーして作った子プロセスで行います。ちなみにこの自プロセスをコピーして子プロセスを作ることを fork ( フォーク ) と呼び、UNIX 系の OS ではよく使用されます。
Apache はサービスが開始されると、自分のプロセスを fork して、リクエストを処理するためのプロセスを起動します。
これは、リクエストが到達してからフォークを行うのでは時間がかかるためです。この事前にフォークしておくことを prefork (プレフォーク) と呼びます。
prefork されたプロセスはリクエストを処理し、数が足りなくなるとさらに fork されます。frok されたプロセスはレスポンスを返してもシャットダウンはされず、引き続き別のリクエストの処理を行います。
Apache のリクエストを処理するプロセスの内の処理は、シングルスレッドで行われるためその中で動作する mod_php はスレッドセーフである必要はないのです。
つまり、Apache 1.x では、IIS 用の ISAPI でおこったような、PHP 拡張のスレッドセーフ云々という問題は、そもそも発生しないのです。
スレッドセーフをサポートしない PHP 拡張がいまだに多く存在している背景には、Apache で PHP を prefork で動作させるぶんには、わざわざよけいな工数をかけて Thread Safe に作成する必要がない、ということもあると考えられます。
Apache 2 のマルチスレッド MPM モード での PHP の動作
Apache 2 からは、マルチプロセスとマルチスレッドを組み合わせた Worker という動作方式が選択できるようになりました。
Worker では、クライアントからのリクエストの処理を、制御用プロセスから fork された子プロセスの上のスレッドが行います。そのため、プロセスベースのサーバよりも少ないシステム資源で、 多くのリクエストに応答することができるようになってます。
IIS ユーザーであれば、この方式が、ワーカープロセス上のスレッドがリクエストの処理を行う IIS の処理方式と非常に似たものであると気が付くでしょう。
この、Apache 2 の Worker = マルチスレッド MPM モードで PHP ランタイムをホストした場合はどうなるでしょう?
答えは PHP ランタイムのインストール Q&A の「なぜ、Apache 2 のマルチスレッド MPM モードを実運用環境で使用するべきではないのですか? 」に記述されています。 (2011/04/06 現在)
内容を要約するとやはり、サードパーティ製のライブラリがスレッドセーフでない可能性があるため推奨できない、旨の記述があり、最後に「マルチスレッド MPM を使用する必要がある場合、 PHP が自分のメモリ空間で実行される FastCGI 設定を観てみてください。」 (原文ママ) との記述があります。
そうです、かつて言われてきた 「IIS 上で PHP は安定して動作しない」というのは必ずしも真ではなく、正しくは「マルチ スレッドでリクエストを処理する Web サーバーでは、PHP は安定して動作しない」 というのが真なのです。
もっとも、「相性の問題」といってしまえばそれまでなのですが…。
IIS の新しいアプリケーションの実行方式
Windows Vista、Windows Server 2008 で搭載されたIIS 7 .0 のリリースに合わせ、IIS 用の FastCGI が開発され、IIS 5.1、IIS 6.0 には追加モジュールとして、IIS 7.x には標準機能として搭載されるようになりました。
FastCGI は、CGI と同じくリクエストの処理をプロセスで行いますが、通常の CGI とは違い、処理が終了しても起動したプロセスをシャットダウンすることはせず、次のリクエストの処理に再利用します。
このため CGI ではすべてのリクエスト毎に発生していたプロセスの起動は、初回リクエスト時と同時アクセス数が増えた場合 (※) にしか発生しないため処理にかかるオーバーベッドは ほとんど問題にならなくなりました。
(※) 同時に動作するプロセスの数は、「最大インスタンス数」として、上限が設定されているため、同時リクエストの数だけいくつも起動するわけではありません。なお、「最大インスタンス数」は任意での指定が可能です。
FastCGI がホストするプロセス内での処理は、シングルスレッドで行われるため、マルチスレッドに対応するためのコードが入っていない ( Non Thread Safe ) ランタイム を使用することができます。
PHP ランタイムにおいては Thread Safe のランタイムよりも Non Thread Safeランタイムの方が高速に動作するため、アプリケーションのパフォーマンスも向上します。
もちろん ISAPI の提供するスレッドモデルでは使用できなかった Thread Safe でない PHP 拡張も安全も使用することができます。
このように IIS 7 .xではFastCGI の搭載により PHP を安全かつ、効率的にホストすることが可能になっています。
その他、PHP アプリケーションのパフォーマンスを上げるための IIS 拡張である Windows Cache Extension for PHP や、PHP ランタイムの設定をユーザーフレンドリーな GUI で行うことのできる PHP Manager 等も提供しており、誤解を恐れずに言えば、現在の IIS 7.x では PHP のホストにおいて、現状考えうるベストな方式を採ることが可能になっています。
こういった状況から「 IIS は PHP の実行に向いていない」という声も、いずれはなくなっていくことでしょう。
まとめ
現在、最新の windows に搭載されている IIS 7.x では、PHP を安全かつ快適にホストすることが可能です。
具体的な手順その他については、我々のチームで作成した以下のドキュメントの内容をご参照ください。
php on windows ガイドライン (ドラフト)
https://technet.microsoft.com/ja-jp/iis/gg535422
もし、お知り合いの方で IIS で PHP をホストすることに不安を感じている人がいらっしゃいましたら、ぜひ一言「心配すんなよ、ぜんぜん大丈夫たぜ!」 とでも言って安心させていただければと思います。