日本オラクル株式会社 コンサルティング統括本部テクノロジーコンサルティング本部 小田 圭二(おだ けいじ)
Javaでは、JDBCによってデータベースへアクセスします。DB接続を表わすオブジェクトを「コネクション」と言い、Connectionインターフェイスで表現されます。コネクションは、データソース(DataSourceインターフェイス)か、DriverManagerクラスのどちらかで獲得します。
データソースはJDBC 2.0から導入されたオブジェクトで、JNDI(Java Naming and Directory Interface)に登録されて使われることを想定しており、コネクションプーリングも考慮した作りになっています。一方のDriverManagerクラスは、JDBC 1.0からある最も古く単純な実装で、単にコネクションを払い出すだけの機能しか持ちませんが、JNDI内でデータソースの定義を記述する際に物理接続の生成元として記述する場合があり、まったく使用しないわけではありません。
データソースを用いたコネクションの取得には、いろいろな方法があります。実は、JDBC 2.0で用意されたクラス群だけでは、コネクションプーリングの枠組みとして解釈がいくつも存在し、ベンダごとに独自の仕組みを持つ実装が提供されました。そのために混乱した時期もありましたが、現在では、データソースが暗黙的にコネクションプーリングの機能を持つという実装でほぼ統一されたようです。 図1は、現在の代表的な取得方法を示しています。
図1 Connectionの取得アプリケーション層ではJNDIにおけるデータソースのリソース位置を指定し、JNDIではファクトリ層とデータソース層でデータソースの実体クラスと実体化方法を定義します。データソース層までで物理接続の実体が定義されていない場合には、ドライバ層でDriverManagerクラス、もしくはデータソースの実体クラスを指定します。
LIST1は、JNDIを使ってjava:/comp/env/MyDsという位置からデータソースを検索し、そのデータソースからコネクションを獲得するコードです。
LIST1 JNDI経由のConnectionクラス獲得この部分だけでは、コネクションプーリングが使用できるかどうかも含めて、データソースの実体クラスが何であるかは分かりません。獲得したコネクションが論理接続なのか、物理接続なのかも分かりません。 データソースの検索にJNDIを使う方法では、データソースの実際のクラスやパラメータなどはJNDIの定義部分に追いやられており、アプリケーション側のコーディングで論理接続のライフサイクルやORA-3113などのOracleエラーハンドリング以外の部分でコネクションプーリングを意識することはありません。ただし、どのようなコネクションプーリングが使用できるかについては、少なくとも開発リーダーやDBAが把握しておき、コーディング方法を指示する必要があるでしょう。
データソースを議論する上で分かりづらいのが、JNDIの仕組みと位置付けです。JNDIを使うプログラミングを知らないとデータソースの入れ替えなどはできないと思いがちですが、すでに用意されたJNDI定義があれば比較的簡単にできます。ただし、その方法はアプリケーションサーバー(以下、APサーバー)やフレームワークごとに異なります。本パートでは、できるだけ簡単に入れ替える方法を紹介します。具体的な方法は後述しますが、共通の考え方を押さえておきましょう。
まず、利用しているAPサーバーやフレームワークのJNDI定義部分、もしくはデータソース定義部分を探します。商用のAPサーバーなどでは、データソース定義部分においてはDriverManagerクラスの定義しかできず、データソースの実体クラスを入れ替えられない場合も多いのですが(図2)、そのような場合でもJ2EEアプリケーションであればJNDI定義は設定できるはずですので、新しいデータソースの定義はこちらの方法を使います(一部設定が困難な実装も存在します)。
図2 設定ツールの制限例
表1に各実装での設定箇所を示したので参考にしてください。
表1 主な実装のデータソース定義とJNDI定義
ただし、データソースのすべてがJNDIの標準のプロパティ設定方法をサポートしていない点には注意が必要です。例えば、Oracle用のデータソースであるOracleDataSourceのコネクションキャッシュは、設定を変更する際に別途プロパティを操作する必要がありますし、商用APサーバーの多くは、JNDI上でのプロパティ設定方法を公開していません(専用のツールなどで設定します)。 また、標準のjavax.sql.DataSourceインターフェイスには必要最低限のプロパティしかないため、データソースの実体クラスごとにプロパティの仕様が大きく異なる点にも注意が必要です。
パート1でも述べましたが、コネクションプーリングを使用する上で、監視をきちんと行なうことは原因不明のトラブルを招かないためにも非常に重要です。商用のAPサーバーにはコネクションプールの状態を監視するツールが付属しているはずですので、監視のためのコーディングは基本的に不要ですが、ツールに表2の項目を監視する機能が含まれていることをチェックしてください。
表2 コネクションプーリングに関する監視/集計項目
含まれていなければ、自作するか、ほかの監視ツールを入手することをお勧めします。監視部分を自作する場合には、表2の項目を監視できるように作成してください。時系列のログを出力できるツールが少ないように思われます。
Javaによる開発では、具体的にどのような点に着目してコネクションプーリングを選択すべきなのでしょうか。そもそも、コネクションプーリングには選択の余地があるのでしょうか。 図3のように、Javaのアプリケーションはさまざまな層で構成されます。
図3 Javaアプリケーションの階層構造とコネクションプーリング
アプリケーションを実装する際には、通常、最も上位の層が提供(もしくは推奨)する機能を使いますので、コネクションプーリング機能についても、最上位の層が提供するものを使用することになります。 しかし、下位の層が持つコネクションプーリング機能のほうが、アプリケーションにとって適切な場合もあります。図3のアプリケーションAでは、JDBCドライバやAPサーバーの提供するコネクションプーリング機能も選択できます。 パート1では、コネクションプーリングにはさまざまな要素があることを紹介しました。アプリケーションの要件によっては、使用するコネクションプーリング機能をより下位のものに変更するか、外部のコネクションプーリング機能を選択するほうが良い場合があります。コネクションプーリングには選択の余地があるのです。
パート1で述べたように、接続時の応答時間は接続時の挙動が「先回り型」である場合に最小化されます。「その場型」の挙動の場合、どうしても初期の接続、もしくは接続がメンテナンスされた後の接続で応答に時間がかかる場合があります。応答時間のぶれが極力少ないことを求められるアプリケーションでは、先回り型の挙動をするコネクションプーリングを選択すると良いでしょう。
APサーバーを1台しか使わない小規模システムでは、アイドル状態のコネクションが多少存在しても大きな問題とはなりません。しかし、APサーバーを数十台も使うような大規模なシステムでは、アイドル状態のコネクションが持つリソースは軽視できません。また、24時間稼動のシステムでは、アイドル状態のコネクションをメンテナンスしなければ、偶然大きく増加したコネクションが永久にそのままということもあります。 筆者としては、次に挙げる条件に当てはまるシステムでは、アイドル状態の物理接続および論理接続をメンテナンスする機能を持つコネクションプーリングの使用をお勧めします。
文キャッシュはほとんどのコネクションプーリングが実装していますが、実装していないものもあります。アプリケーションの種類を問わず、デメリットがほとんどなく、パフォーマンスの向上が見込める文キャッシュを使用できるコネクションプーリングを使用すべきです。
コネクションプーリングの動作を監視できる機能は、システムが巨大化/複雑化するにつれ、必須になってきます。何か問題が発生した場合の原因切り分けの速さが違います。多くの商用APサーバーにはWebブラウザ上から監視できる機能がありますし、そうでなくとも、コネクションプーリング関連クラスのメソッドから各種情報を取得できることがほとんどです。コネクションの数が予想しにくいWebアプリケーションの場合は特に、監視する手段を持つコネクションプーリングが有効です。 また、アラート機能を備えるコネクションプーリングもあり、こちらは運用監視システムとの連携など、きめ細かいリアルタイムの監視を行なう場合にお勧めします。
コネクションプーリングの選択に実機検証などの時間をかけられない場合でも、デフォルトで使用するコネクションプーリング機能に問題ないかどうかを、机上で結構ですので、次節で紹介する表と照らし合わせて検証してみてください。問題のある場合には、JDBCドライバのコネクションプーリングに切り替えて使用するなどの対応策があることを覚えておきましょう。
ここでは、JavaアプリケーションからOracleデータベースに接続するためのコネクションプーリングの実装を紹介します。前節までの動作詳細とともに、適切なコネクションプーリング機能を持つコンポーネントを選択する参考にしてください。
表3 Oracle Connection CacheおよびDBCPが備えるコネクションプーリング機能
暗黙コネクションキャッシュでは、高速接続フェイルオーバーの機能を使用できます。これはOC4J 10gから追加された機能で、Real Application Clusters環境において、データベースに障害が発生したかどうかをほぼ100%の正確さで判断できます(図4)。
図4 高速接続フェイルオーバー
この機能はONS(Oracle Notification Service)によって実現されており、今まではSQL文を発行し、その結果から類推するしかなかったデータベースサーバー(以下、DBサーバー)の障害を、DBサーバーから直接メッセージとして受け取ることができます。イベントドリブンなので、誤検知を排除し、検知も最も高速に行なえます。また、障害から復旧したことも通知として受け取り、コネクションプールを復旧ノードへ再分散できます。
Oracle Connection Cacheは、ほかにも大規模なシステムに対応するための機能を備えており(コラム「Oracle Connection Cacheの柔軟なコネクション管理機能」を参照)、障害時に際しても、Oracleデータベースを使用したシステムでは現時点で最高の機能を備えます。ただし、監視に関してはそのままでは簡単には閲覧などを行なえないため、機能を自作する必要があります。監視に必要な情報に関しては、すべてメソッドを通じて取り出せます。
Oracle Connection Cacheには、異なるユーザーの接続を1つのキャッシュでまとめて管理する機能も備わっています。適切なユーザーの接続をキャッシュから選択する作業は、暗黙コネクションキャッシュが行なってくれます。
ほかにも、最大物理接続数を超えた時点でのロギングなど、論理接続の獲得/返却時に実行したい動作を埋め込むために、OracleConnectionCacheCallbackというコールバックインターフェイスも用意されています。ユーザーはこのインターフェイスを継承するクラスを作成してデータソース経由で登録することにより、獲得/返却時にトリガーのように処理をフック(差し込み)できます。
DBCPには、BasicDataSourceクラスという基本的な実装のデータソースが用意されており、こちらをそのまま利用することでも十分なコネクションプーリング機能が使用できます。機能の概要は表3をご覧ください。
しかし、BasicDataSourceクラスだけではDBCPをほとんど使いこなしていません。DBCPは奥が深いAPIで、その基礎となっているのはオブジェクトキャッシュのためのAPI「Java Commons Pool」です。DBCPは全面的にこのJava Commons Poolを使用する形で提供されています。 DBCPを利用してカスタムのコネクションプーリング機能を実装するのは簡単ではありませんが、最も難しいキャッシュ管理部分やXA部分、文キャッシュなど、かなり細かな機能ごとにコネクションプーリング管理メソッドおよびプロパティが用意されているため、非常に高機能な実装の割にはシンプルな構成が可能です。DBCPの全体像概要は図5をご覧ください。
図5 DBCPカスタムの全体像概要 DBCPをカスタムで書くと、BasicDataSourceクラスでは実現不可能なことも実現できます。ほかの実装では満足できない厳しい仕様が求められる際には、カスタムでの利用をお勧めします。表4 主要なAPサーバーのコネクションプーリング機能(1)
表5 主要な商用APサーバーのコネクションプーリング機能(2)
詳しくは、各APサーバーのマニュアルなどをご覧ください。文キャッシュが使用できなかったり、監視機能が不十分なものもありますし、ここに挙げたものは先回り型の接続を行なえません。「商用=高機能」ではないことに注意してください。ここからは、APサーバーやフレームワークにデフォルトで使われているデータソースをほかの実装に変えて、コネクションプーリング機能を切り替える具体的な方法を説明します。対象データベースをOracleとして、いずれもOracleDataSourceへの切り替えを行なっていますが、手順はほかのデータソースへの切り替えも基本的な部分では同様です。
Tomcat 5.0では、$CATALINA_HOME/conf/<ホスト名 >/<コンテキスト名>.xmlというXMLファイルでデータソースを定義しています。このファイルを直接書き換えても良いですし、Webブラウザを使った管理ツール経由でも設定できます。ここでは、DBCPで動作していたデータソースをOracleDataSourceの暗黙コネクションキャッシュに切り替えます。
LIST3 書き換え後のsample.xml
ファクトリ | BasicDataSourceFactoryをOracleDataSourceFactoryに変更します。必須です |
driverClass | 削除します。必須です |
暗黙コネクションキャッシュ | connectionCachingEnabledをtrueにして暗黙コネクションキャッシュを有効にします。必須です |
暗黙文キャッシュ | BasicDataSourceでも使用できましたが、改めて追加します。implicitCachingEnabledをtrueにします |
validationQuery以下のプロパティ | LIST2中のBasicDataSource固有のプロパティ設定は、OracleDataSourceの設定ファイルには書けませんので削除します |
LIST4 プロパティ設定メソッド
ここではプロパティの各設定値を直に書いていますが、プロパティらしく外部ファイルに定義して読み込む実装のほうがスマートです。注1:本来はConnectionsオブジェクトが持つプロパティ。デフォルトはtrueで、SQLの実行ごとにコミットが発行されてしまう。ほとんどのアプリケーションでは前もってfalseにしておかないと、トランザクション処理を記述できない。
Strutsでは、WEB-INF/struts-config.xmlというXMLファイルでデータソースを定義しています。外部ツールを使用しない限り、基本的にはこのファイルを直接書き換えます。ここでは先のTomcatと同じく、DBCPで動作していたデータソースをOracleDataSourceの暗黙コネクションキャッシュに切り替える例を示します。
LIST5 書き換え前のstruts-config.xml
LIST6 書き換え後のstruts-config.xml
LIST7 Strutsにおける一般的なデータソースの取得
注2:Strutsで定義されるMVCモデルで言うModel部分の雛形クラス。Actionクラスを継承したクラスにアプリケーションロジックを記述する。
注3:Actionクラスに定義されている独自メソッドで、JNDIを隠蔽した形でデータソースを取得できる。
それを解決する方法の1つがLIST8で、Actionクラスを継承してプロパティを設定するための抽象クラス(LIST8ではExtendActionクラス)を作成し、アプリケーションロジックはその抽象クラスを継承したクラスに記述します。 LIST8 コネクションキャッシュのプロパティ設定に対応OC4Jでは、<AS_HOME>/j2ee/home/config/data-sources.xmlファイルでデータソースが管理されています。Strutsと同様、このXMLファイルを直接書き換えて設定を変更します。ここでは、OC4J管理下のデータソースから、OracleDataSourceの暗黙コネクションキャッシュへ変更する例を紹介します。 なお、ここで使用するのは最新バージョンのOC4J 10gとします。
LIST9 変更前のdata-sources.xml
Managed Data Sourceでは、コネクションプーリング機能をOC4Jのモジュールが提供しますので、当然、細かなプロパティもすべてこのファイルの中に記述できます。Sun ONEでは、server.xmlファイルを直接編集するか、または開発ツールであるSun ONE Studioから設定することで、コネクションプーリングを行なうデータソースを変更できます。ここではSun ONE Studioからの設定例を紹介します。
これらのAPサーバーでは、コネクションプーリングを行なうデータソースを変更するインターフェイスを用意していません。開発者自身でJNDIツリーを作成し、データソースを登録する必要があります。その作業は本特集の説明範囲を越えますので、手順の概要だけを説明します。ここまでと同じく、OracleDataSourceの暗黙コネクションキャッシュを使用する方法を取り上げます。
WebLogic Serverや Borland Enterprise Serverでは、前出の表1のように、JNDIツリーを変更するインターフェイスが用意されています。このJNDIツリーに、DataSourceインターフェイスを持ち、実体がOracleDataSourceであるリソースを作成します。設定できるプロパティについては、ここで設定しておきます。その後、アプリケーション側から参照できるようにバインドも忘れずにしておきます。あとは、Tomcatなどと同様の手順で登録したデータソースを利用できます。
WebSphere Application Serverでは、JNDIツリーを自由に変更できるインターフェイスが用意されていないので、自分でJNDIツリーを構築する必要があるものと思われます。ファイルベースでのJNDIツリーを一から構築するのは、まさに本格的なJNDIプログラミングとなります。手間を考えると、Strutsなどのフレームワークを使うか、そのまま用意されているコネクションプーリング機能を使用することをお勧めします。
コネクションプーリングは、.NETアプリケーションでも当然、利用される技術です。そこで次に、.NETアプリケーションにコネクションプーリング機能を提供するミドルウェア「ODP.NET」を解説します。
.NETの標準的なDBアクセスコンポーネントであるODBC.NETやOLE DB.NETは、基本的にコネクションプーリング機能が実装されていません。そのため、DBを使用する処理が始まるたびにDBサーバーへのコネクションの生成が発生します。当然、WebアプリケーションなどではOracleに対してパート1で説明したような過負荷の問題が発生してしまいますので、コネクションプーリングやそれに似た仕組みを自作するなど、アプリケーション側での解決が必要となります(図6)。
図6 .NETのコネクションに関する問題
なぜ、Javaでは乱立状態に近いコネクションプーリング技術が、.NET環境では普及していないのでしょうか。これには理由があります。まず、.NET系でコネクションプーリングが必須となるような大規模なWebアプリケーションの開発実績が、IIS(Internet InformationServices)に集中している点です。
確かに、IIS(ASP.NET)ではODBC 3.0のコネクションプーリング機能を使用してパフォーマンスを向上させることができます(図7)。
図7 IISでのコネクションプーリング
しかし、マイクロソフトのナレッジベースKB:164221に「INFO:How to EnableConnection Pooling in an ODBC Application(ODBCアプリケーションでコネクションプーリングを可能にする方法)」という情報はありますが、例えば、C#.NETでコーディングなしにコネクションプーリング機能を使用する方法などの解説が見あたりません。
ただ、.NETでもIIS以外のAPサーバーが独自のコネクションプーリングを実装すれば、Javaと似たような状況になるかもしれません。
例えば、ADO.NETではデータセットに対してFillメソッドを実行するだけで、テーブル単位でデータを取得できます。JavaのようにResultSetオブジェクトにnextメソッドを使って1行ずつ読み出す(フェッチする)といったコードを書かなくても、同様の処理を行なう方法が多く提供されています。
とても便利に見えますが、Fillメソッドのようなデータベースアクセス隠蔽技術を多くのユーザーが利用するアプリケーションで使うのは、以下に挙げる点でとても危険に思えます。
まず、明らかに不要なデータまでフェッチしていないかどうか。100万件のデータのうち、最初の10件だけを画面に表示する場合に、100万件にフェッチするのは大変もったいない動作です。
次に、不要なデータをロックしていないか。ほかのユーザーもロックするかもしれないのに、とりあえず表全体をロックしておくという処理が、意図しない間に行なわれてしまう場合があります。
さらに、すでに変更されているかもしれない古いデータをもとに処理を行なっていないか。ほかのユーザーによって変更される可能性のあるデータに対し、最低限の範囲でロックをかけてから参照するという処理は、データベースアクセス隠蔽技術を使ったプログラミングでは記述できない場合があります。
便利なオブジェクトの動作がはっきりしない場合、OracleではSQLトレースを観察するなどして、無駄なアクセスがないかどうかを確認するようにしましょう。
ODP.NET(Oracle Data Provider .NET)は、オラクルが提供する.NETアプリケーションからOracleデータベースに接続するためのアクセスコンポーネントです(図8)。
図8 ODP.NET
ODP.NETが登場して、ようやくOracleを利用するすべての.NETアプリケーションが簡単にコネクションプーリングの恩恵を与ることができるようになったと言っても過言ではないかもしれません。 ODP.NETはOracle Clientライブラリをネイティブで用いるため、コネクションプーリング以外の処理も高速です。Oracleを利用するアプリケーションを作成する場合は、クライアント側/サーバー側を問わず、利用することをお勧めします。
ODP.NETのデメリットと言えば、インストールに手間がかかることです。ほかの.NETモジュールのようにWindows Updateで配信されるわけではなく、クライアント1台1台にData Accessモジュール 注4をインストールしていかなければなりません。
インストールされるモジュールは、OracleClientのOCIクライアント部分とODBC.NET、OLE DB.NETのOracleモジュール、およびODP.NETです。
注4:このモジュールは、Oracle Technology Network JapanのWebサイトから無償でダウンロードできます( http://www.oracle.com/technology/global/jp/software/tech/windows/odpnet/index.html)。
ODP.NETのコネクションプーリングは、独特の仕組みで暗黙的に動作します。OracleConnectionクラスでDBサーバーへのコネクションを生成する際、ConnectionStringフィールドにコネクションプーリング関連のパラメータ文字列を埋め込むことによって、コネクションプーリングが機能し始めます(LIST11)。
LIST11 コネクションプーリングの使用
コーディング上、コネクション生成の仕組み自体には、まったく変化はありません。 表6にコネクションプーリング関連のパラメータを示します。
表6 コネクションプーリング関連のパラメータ
プールされるコネクションは、コネクションプーリングに関するパラメータも含めたConnectionStringプロパティの値で識別されます。ConnectionStringの値がコネクションプール名の代わりとなるわけです(図9)。
図9 コネクションプールの識別子
ODP.NETにもメンテナンススレッドは存在し、「レギュレータスレッド」と呼ばれます。アイドル物理接続の切断など、行なうことはJavaでのメンテナンススレッドと同等です。
パート1からここまで述べてきたように、コネクションプーリングにはさまざまな機能が必要です。ODP.NETにはまだ足りない機能があることを把握した上で利用してください。
.NET環境でアプリケーションを作成する場合、Javaと異なりコネクション生存期間が分かりづらいことがあります。Javaではコネクションの獲得を明示的に行なうスタイルが一般的なため、トランザクションの生存期間とコネクション生存期間を一致させることは容易ですし、実例も多く紹介されています。
しかし.NETでは、例えばデータアダプタやデータセットなどを用いるなど、コーディングを行なうことなくデータベースとの接続部分を実装する方法が数多くあります。気づかないうちにコネクションプーリング機能を使用していながら、意図せずに長時間コネクションを獲得したままになることがないよう、ツールが自動的に生成したコードをチェックすることをお勧めします。
コネクションプーリングは、疎な関係のオブジェクトを正しくキャッシュするための技術として発展してきました。特にWebアプリケーションでは常識的なものとなっています。
本特集では、コネクションプーリングの仕組みや実装の切り替え、.NETでのコネクションプーリング事情などをある程度掘り下げられたと思います。さまざまなコネクションプーリングを取捨選択する文化が生まれれば幸いです。Javaでは究極のコネクションプーリングが生まれる日も近い気がします。.NETにも、DBCPやコネクションキャッシュのような実装が期待されるところです。
少しでもコネクションプーリングで解決できるパフォーマンストラブルが減り、設計の困難さが和らげばと願っています。
Copyright © 2009, Oracle Corporation Japan. All rights reserved.
無断転載を禁ず
この文書はあくまでも参考資料であり、掲載されている情報は予告なしに変更されることがあります。日本オラクル社は本書の内容に関していかなる保証もいたしません。また、本書の内容に関連したいかなる損害についても責任を負いかねます。
Oracleは米国Oracle Corporationの登録商標です。文中に参照されている各製品名及びサービス名は米国Oracle Corporationの商標または登録商標です。その他の製品名及びサービス名はそれぞれの所有者の商標または登録商標の可能性があります。
小田 圭二(おだ けいじ) 1996年日本オラクル入社。人事教育本部にて、新卒や中途採用社員に対し、データベースやOS、ネットワークの講師を5年ほど経験した後、2000年にテクノロジーコンサルティング本部に異動。 テクノロジーのコンサルタントとして、主に大規模ミッションクリティカルシステムを担当。 ポリシーは、「OracleもOS上で動くアプリケーションにすぎない。だから、OS、ストレージ、ネットワークを学ぶべき」。 スキル面の興味は、アーキテクチャ、DBA、インフラ技術、教育、コンサル手法など。