PR

 問題2の原因は複数の観点からとらえられます。以下,簡単な原因から複合的な原因まで説明し,解決策として挙がった方法を順番に説明します。

Webサーバーのスレッド数が上限に

 問題が発生したとき,Webサーバーのスレッド数が上限に達していたことが明らかになりました。Webサーバーのスレッド数が上限に達していると,クライアントPCからの新たなログインを受けることができず,利用者によってはその症状が「システムの停止」と見えたようです。

 支社には約100台のクライアントPCがありましたが,Webサーバーに設定していたスレッド数の上限は40でした。一見すると少ない値のようですが,この値はDBサーバーに対するコネクション・プールの数や,Webサーバーに搭載しているメモリー量などから計算した妥当な値でした。とはいえ,上限に達してしまったのですから,その値を大きな値にすると効果がありそうです。ですが,単純に増やしても問題は解決しません。その理由を次に説明します。

 ここでは費用対効果の観点で見ていきます。ほとんどのシステムでは費用対効果を求められますが,それを厳密に計算して求めている例は,少なくとも筆者が知る限りはありません。早い話が,費用は少なければ少ないほど良いという選択です(それはそれで一つの見識であり,ある意味正しいと思います)。そのため100クライアントが存在したとしても,100クライアントが余裕を持って処理可能なリソースをシステムが持つことはありません。この支社のケースでは,(平常時のWebサーバーの実働時間のレポートから)スレッドの上限が40でも平常時のリソースに余裕がある,という判断がなされていました。仮にスレッドの上限を大きな値にすると,その上限値に応じてサーバーのリソース(メモリーなど)を増やすことを検討しなければならず,それは投資対効果の観点から妥当な策とはいえません。

 こうしたことから,「スレッド数の上限を大きな値にする」という案は不採用となりました。

一般の利用者はPCの電源を切ってしまう

 このシステムのユーザー・インタフェースは,Internet Explorerの「キオスクモード」を利用し,メニューなどを隠した上で全画面表示にしていました。マウスの右クリックも無視し,問題が起こって待ち状態になっていても,その状況を利用者に知らせる画面などはありません(図3)。

図3●修正前の画面イメージ
図3●修正前の画面イメージ

 もし皆さんが,Webブラウザを利用していて,なかなか応答がなかったらどうするでしょうか。筆者なら中止ボタンをクリックして再読み込みするか,あきらめてそのウインドウなりタブなりをクローズし,別の作業を始めます。ただこのシステムの場合,中止ボタンは画面のどこにもなく,マウスを右クリックしてもメニューは出ません。

 Windows OSに詳しければ別の手段を取る注1のでしょうが,一般的な利用者なら,PCの電源を切ってしまうことが十分に考えられます。今回のケースでも一部の利用者はそうした行動を取っています。いきなりPCの電源を切ると,TCPの制御メッセージ(RSTやFINなど)はサーバーへ送られません。Webサーバーは現在処理中のクライアントPCが接続を打ち切ったことに気づけず,Webサーバーのスレッドは利用中の状態のまま動作することになります。場合によっては,同じクライアントPCから新たなリクエストを受け取ることもあります。

 このような強制的な再試行が考えられますので,障害が起こると,たとえスレッド数を増やしていても,スレッド数の上限に達してしまう可能性が高くなります。つまり,問題は解決しないということです。

 ならば,ユーザー・インタフェースを変更し,障害時に使うためのボタンを用意(処理を中止する機能を実装)するという案があります。しかしそれはあまり良い案ではありません。仮にクライアントPCからの接続打ち切りをWebサーバーが検出したとしても,実行中のスレッドを停止させるとリソース・リークの可能性があります。具体的にはThreadクラスにdestroyというメソッドがありますが,問題が多いのでdeprecated(使用を勧められない)になっています。

 こうして,「メニューを加えて処理を中止できるようにする」という案も不採用になりました。

タイムアウトを設定できないクラスを使っていた

 このシステムはJ2SE(Java 2 Standard Edition) 1.4.2で開発されていました注2。本部サーバーのWebサービスとHTTPで対話するために利用しているのは,「java.net.HttpURLConnection」クラスのオブジェクトです。Java5より前のHttpURLConnectionには,業務システムの対話処理に利用するには致命的とも呼べる欠点があります(HttpURLConnectionクラスはJ2SE標準のクラスなので,このクラスを使った開発者を責めることはできません)。具体的には,読み込みタイムアウトの設定が不可能なことです注3。このため,ネットワーク障害によってサーバーからのレスポンスを長時間にわたって受信できなくても,異常を検出できず,待ち状態が長く続くことになります。

 とはいえ,J2SEをバージョンアップし,Java5以降を用いてシステムを再構築するというのは,障害対応の範囲を超えてしまいます。現状のJ2SE 1.4.2のままで対応する場合,以下の方法が考えられます。

(1)タイムアウトの設定が可能で,定評のあるHTTPクライアント・ライブラリを利用する

(2)独自のHTTPクライアント・クラスを開発する(java.net.Socketを利用)

(3)アプリケーションから直接HttpURLConnectionクラスを利用するのではなく,別のスレッド(ワーカー・スレッド)を生成してそこでHttpURLConnectionクラスを利用する。アプリケーションはワーカー・スレッドからの受信通知を受け取るか,またはタイムアウトするまで待機する

 (1)を選択した場合,期待通りの動作が可能かをソースレベルで検証する必要があります。最も信用できるJava標準のHttpURLConnectionクラスの実装が障害の一つの原因となったわけですから,ソースコード・レベルで検証すべきです。ですが,公開されているライブラリは機能が豊富なので読解すべきソースコードも多く,できれば採用したくない方法です。

 この中で一番筋が良い方法は(2)です。本部サーバーのWebサービスとの処理が可能となる最低限の仕様を実装すればよいからです。次に起こるかもしれない未知の障害への対応を考えた場合,最低限の実装しか持たず,ソースコード・レベルで完全に把握しているライブラリを利用するのが一番確実です。とはいうものの,この案に対してはインフラとなるソースコードを抱えることに対する危惧などの反対案もあり,結局採用されませんでした。また,もう一つの反対理由として,適切なタイムアウトの判定が難しいという意見も上がりました。

 (3)は一見すると極めて真っ当な方法に見えますが,タイムアウトした場合,ワーカー・スレッドをどうするかという点について良い方法が見つからないという問題があります。ワーカー・スレッドを放置しておけば,同様な障害発生時に無用なスレッドを大量に生成しかねません。結局!)も採用されませんでした。

 なお,解決策について意見交換しているとき,なぜ「MOM(Message-Oriented Middleware)」を採用しなかったのか,という疑問が出されました。このケースの場合,Webサービスを利用しているといっても本質的には会話処理であり,メッセージをキューイングすることで解決できるわけではありません。また,MOMを用いた場合,キューに大量にメッセージがたまった状態で障害が発生すると,調査と復旧に掛かる時間は,Webサービスを用いた場合より格段に長くなります。