【トラブル大捜査線】失われたコネクションを追え!現場から学ぶWebアプリ開発のトラブルハック(7)(3/3 ページ)

» 2007年09月25日 00時00分 公開
[金子崇之NTTデータ先端技術]
前のページへ 1|2|3       

【最終話】それが、トラブルシューティング屋の務め

 クローズ漏れの個所も大体の見当が付いたので、アプリケーション開発チームに確認・修正を依頼した。

 アプリケーションが直るまでの間、removeAbandonedを付けたままで負荷試験を継続することにした。目的はremoveAbandonedのオーバヘッドを測定することである。logAbandonedを指定しなければstackTraceは生成されない。そのため、ある程度の性能は出るように思われた。

removeAbandonedの同期化

 しかし、実際に測定してみたところ、思ったように性能は出なかった。CPUがほとんど使い切れなかったのだ。

 ここまできたら、ついでに解析してしまうのが、トラブルシューティング屋の務めだろう。負荷を掛けている最中にスレッドダンプを取得すると、多数のスレッドが以下の2つの処理の直前でtraceリストのロック取得待ちとなっていた。

  • コネクションを取得する際のtraceリスト内の走査処理
  • コネクションを返却する際のtraceリストからのremove処理

 該当するAbandonedObjectPoolのソースコードをチェックすると、リスト6のようになっている。

リスト6 removeAbandonedのtraceリストロック
private void removeAbandoned() {

……省略……

    synchronized (trace) {
        Iterator it = trace.iterator();
        while (it.hasNext()) {
            AbandonedTrace pc = (AbandonedTrace) it.next();
            //タイムアウトしていなければ次のコネクションへ
            if (pc.getLastUsed() > timeout) {
                continue;
            }
            //タイムアウトしていれば、removeリストに追加
            if (pc.getLastUsed() > 0) {
                remove.add(pc);
            }
        }
    }

……省略……


}

public void returnObject(Object obj) throws Exception {
    if (config != null && config.getRemoveAbandoned()) {
        synchronized(trace) {
            //traceリストから除去
            boolean foundObject = trace.remove(obj);
            if (!foundObject) return;
        }
    }
    super.returnObject(obj);
}

 synchronizedブロックで囲われた中で、それぞれIteratorによるリストの走査処理と、ArrayList#remove(obj)を呼び出している。

 どちらも十分速い処理ではあるが、リスト内の要素数が増えるにつれ、どうしてもある程度の時間を要してしまう。この2つの処理は、コネクションの使用に伴い毎回呼ばれる処理のため、無視できない同期待ち時間になってしまったのだ。

「このような基本的なミスをして申しわけない」

 removeAbandonedのオーバヘッドは予想以上に大きかった。やはり、コネクションのクローズ漏れに関しては、アプリケーションを修正せざるを得ない。

 「このような基本的なミスをして申しわけない」。アプリケーション開発のリーダーから修正版のアプリケーションを受け取ると、今度こそ性能が出ることを確認できた。

【Tips】DBCPのパラメータを理解し、武器にする

 今回の事例からは、Tomcatを使用するうえではDBCPについての知識も必要となることが分かる。そこでコネクションプールの性能に影響を与えるパラメータについて解説しておこう。

コネクション数を管理する

表1 コネクション数の管理
パラメータ 内容 デフォルト値
maxActive プールから貸し出すコネクションの最大数 8
minIdle プール内のコネクション数をminIdle以上に保つ 0
maxIdle プール内のコネクション数をmaxIdle以下に保つ 8
initialSize プールの初期状態に確保するコネクション数 0
maxWait プール内のコネクションが不足したときの最大待ち時間(ミリ秒) −1(無限大)

 コネクション数の管理において、一番注意が必要となるのが、「maxActive」だ。DBサーバ側にも最大プロセス数等の制限があるため、その値以下にしなくてはならない。

 また、アプリケーションサーバが複数台あるときには、その分も考慮しなくてはならないだろう。0以下の値を指定した場合、最大値の制限はなくなる。

 「maxWait」はプール内のコネクションが不足したとき、最大何ミリ秒待つかを指定する。指定した時間内にコネクションが確保できなかった場合、SQLExceptionが投げられる。エラーとして応答を返せば、アプリケーションが無応答になることを防ぐことができる。

 コネクション数はTomcatのスレッド数よりも少ない値を指定することも可能だ。しかし、Object#wait()で待っている複数のスレッドはObject#notifyAll()によってすべて起動させられる。

 コネクションを取得できるのは、OSのスケジューリングにより最初にコネクションの再取得にたどり着いたスレッドのみだ。そのため、運が悪いスレッドはいつまでもコネクションが取得できないこともあり得る。コネクション数を絞り込む場合、適切な「maxWait」の値を設定しタイムアウトさせた方がよいだろう。

コネクションを検証する

表2 コネクションの検証
パラメータ 内容 デフォルト値
validationQuery コネクションが有効かどうかを検証するためのSQL なし
testOnBorrow コネクションをプールから取り出すときに検証するかどうか true
testOnReturn コネクションをプールに返すときに検証するかどうか false
testWhileIdle プール内でアイドル状態のコネクションを検証するかどうか false
timeBetweenEvictionRunsMillis アイドル状態のコネクションの検証間隔 −1
numTestsPerEvictionRun 1回の検証当たり何個のコネクションを対象とするか 3
minEvictableIdleTimeMillis 何ミリ秒アイドルだったら検証するか 1000 * 60 * 30

 validationQueryパラメータでは、コネクションが有効かどうかを検証するためのSQLを指定する。通常は、負荷の軽いSQLを指定する。

 testWhileIdle以降はプール内でアイドル状態のコネクションを検証するかどうかのパラメータだ。この機能を使用するときには、validationQueryパラメータが設定されている必要がある。

PreparedStatementをキャッシュする

表3 PreparedStatementキャッシュ
パラメータ 内容 デフォルト値
poolPreparedStatements PreparedStatementをキャッシュするかどうか false
maxOpenPreparedStatements 1コネクション当たりキャッシュするPreparedStatementの最大数 0(無限大)

 PreparedStatementキャッシュは、作成したPreparedStatementをDBコネクションごとにキャッシュする機能だ。SQLのパース、実行計画の作成などのDBサーバの処理を減らし、負荷を下げる効果がある。

コネクションを追跡する

表4 コネクションの追跡機能
パラメータ 内容 デフォルト値
removeAbandoned クローズ漏れとなったコネクションを回収するかどうか false
removeAbandonedTimeout コネクションが最後に使用されてから回収対象となるまでの時間(秒) 300
logAbandoned コネクションがプールから取り出されたときの時間とスタックトレースを回収時に出力するかどうか false

 removeAbandonedはクローズ漏れとなったコネクションを回収する機能だ。しかし、本文中でも触れたとおり、非効率な処理が多く含まれている。本番環境で使用することは避けた方がよいだろう。

【最後に】閉め忘れにご用心

 本稿では、試験環境におけるDBCPに関するトラブル事例を紹介した。DBCPにはクローズ漏れを実行時に回収する手段や呼び出しのstackTraceを表示する機能が存在する。

 しかし、フレームワークにコネクションの処理を委譲したり、コーディング規約やチェックツールにより確実にクローズしていることを確認したりするなど、クローズ漏れに関してはそのほかの手段を用いる方がよいだろう。

 あくまで緊急の手段であることをお忘れなく。

プロフィール

金子崇之

金子 崇之(かねこ たかゆき)

NTTデータ先端技術株式会社 オープンソース事業部所属。
入社よりJavaを用いたWebシステムの開発支援にかかわる。最近では、主にオープンソースのアプリケーションサーバに関する検証や技術支援、トラブルシューティングに明け暮れている



前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。