プログラマが知るべき97のこと/プロセス間通信とアプリケーションの応答時間の関係

応答時間は、ソフトウェアの使いやすさを大きく左右します。何か操作をして、その応答を長く待たされることほどストレスのたまることはないからです。特にその性質上、使用中に何度も「入力と応答」を繰り返すソフトウェアの場合は応答時間が重要になります。応答時間が長ければ、ソフトウェアのせいで自分の時間を無駄にされ、生産性も下がったとユーザは思うでしょう。しかし、なぜ応答時間が長くなるのか、その原因は十分には理解されていません。最近はその傾向か顕著なようです。ソフトウェアのパフォーマンスについて触れた文献は多くありますか、その多くがいまだにデータ構造とアルゴリズムに注目しています。もちろんその2つが大きく影響することもありますが、最近増えてきたマルチティアのエンタープライズアプリケーションの場合、そういうことは少ないのです。

私の経験から、この種のアプリケーションでパフォーマンスが問題になった時、データ構造やアルゴリズムを調べて改善を図るのは得策ではないと言えます。この種のアプリケーションの場合、応答時間を大きく左右するのは、リモートプロセス間通信(IPC:Inter-process Communication)の数です。入力への応答にIPCがいくつ必要になるかで、パフォーマンスが大きく変わるのです。他にも個々のソフトウェアに特有のボトルネックはあるでしょうが、通常はリモートIPCの影響が最も大きくなります。リモートIPCか行われると、たとえそれが1度だけでも、ソフトウェア全体の応答時間を無視できないほど遅延させます。リモートIPCがいくつも続けて発生すれば、それが積み重なって遅延が極端に大きくなってしまうのです。

その代表例が、オブジェクトリレーショナルマッピングを利用したアプリケーションで行われる「リップルローディング」です。リップルローディングとは、オブジェクトグラフを作成する際に、グラフ作成に必要なデータを取得するために何度もデータベース呼び出しを繰り返すことです(Martin Fowler 著「エンタープライズアプリケーションアーキテクチャパターン」の「遅延ロードjの頁を参照)。データベースのクライアントがWebページをレンダリングするミドルティアのアプリケーションサーバである場合、通常、データベース呼び出しはシングルスレッドでシーケンシャルに実行されます。その一つ一つで発生した遅延が積み重なり、全体の応答時間に大きな影響を与えるのです。個々のデータペース呼び出しの所要時間が10ミリ秒だとしても、1,000回の呼び出しが必要になれば(そのくらいは珍しくありません)、全体の応答時間は10秒を超えてしまう計算になります。他に同様の遅延が起きる処理の例としては、Webサービスの呼び出し、WebブラウザからのHTTPリクエスト、分散オブジェクトの呼び出し、要求一応答メッセージング、カスタムネットワークプロトコルを通じたデータグリッドとのやりとりなどがあげられます。入力への応答に必要になるリモートIPCが多ければ多いほど、応答時間も長くなります。

リモートIPCの数はどうすれば減らせるでしょうか。そのための方法は様々ですが、比較的簡単でよく知られている方法もいくつかあります。その1つは、「思考節約の法則(Principle of Parsimony)」を応用する方法です。これは、具体的には、プロセス間のインタフェースを最適化し、本当にいま必要なデータだけを必要最小限のインタラクションで取得するという方法です。他には、IPCをできるだけ並列化するという方法もあります。そうすれば、全体の応答時間は、主に最も遅延の大きいIPCによって決まることになります。また、以前のIPCの結果をキャッシュするという方法もあります。キャッシュを利用することで、その後に必要になるIPCの数を減らそうというわけです。

アプリケーションの設計にあたっては、入力への応答に必要なIPCの数が多くならないよう配慮すべきでしょう。パフォーマンスの悪いアプリケーションをよく調べてみると、1つの入力への応答に、IPCが数千も必要になっているということがよくあるのです。データ構造やソートアルゴリズムを工夫するよりも、キャッシュや並列化などのテクニックによって、入力に対するIPCの数を減らす方がはるかに効果的でしょう。