Object Poolパターン
Object Poolの必要性
今回はObject Poolパターンについて説明します。Object Poolは、オブジェクトの生成に関するパターンでオブジェクトの数を制限して再利用したい場合に利用するパターンになります。このパターンを利用するケースとして、オブジェクトの生成にコストがかかる場合や、オブジェクトをある数だけ用意して並行処理を行うような場合が考えられます。具体的には、データベースのコネクションやEJBオブジェクトなどにオブジェクトプールが利用されています。
データベースへのアクセスでは、コネクションの確立にコスト(時間)がかかります。そのため、コネクションプールを使って、データベース接続のコストを下げ、接続を再利用しています。Webシステムでは、図1のようにマルチスレッドで動作しているサーブレットから呼び出される処理(Business Logic)の中で、コネクションを取得してデータベースアクセスを行います。コネクションプールを使っている場合、DataSourceは、起動時にあらかじめコネクションオブジェクトをいくつか用意しておきます(*1)。DataSourceに対して、getConnectionメソッドを呼び出したときには、すでに用意されているプールから、コネクションオブジェクトが返されます。
Business Logicでは、コネクションを使って処理を実行した後、コネクションをクローズします。コネクションプールを使わない場合は、コネクションをクローズすると実際のデータベースへの接続も終了してコネクションオブジェクトも消滅しますが、コネクションプールの場合は、クローズしてもコネクションオブジェクトがプールに戻されるだけで、実際のデータベースへの接続は終了しません。プールに戻されたコネクションオブジェクトは、利用可能状態(未利用状態)になり、次の処理で再利用されます。
*1:起動時に用意されるコネクションの数は設定によって変わります。設定によっては起動時に用意される数は0にしておいて、必要になってからコネクションを用意する(接続を行う)といったことも可能です。
コネクションプールのメリット
こうしたコネクションプールの利用の目的はスループットの向上です。データベースへの接続は、一般に数十ミリ秒から数百ミリ秒以上かかる「重たい」処理ですが、コネクションプールを使うことでBusiness Logicがコネクションオブジェクトの取得するににかかる時間が数ミリ秒ですむようになります。結果として、Business Logicの処理時間が短くなり、全体の単位時間あたりの処理量(スループット)も向上します。
実際に、単純なデータベースアクセスを行う多数のリクエストを処理する場合、コネクションプールの有無によってスループットが10倍~数十倍違うという結果がでます。
コネクションプール利用のもう1つの利点は、コネクションの上限を制限できることです。コネクションプールに設定された最大値を超えてコネクションを取得しようとすると、コネクションオブジェクトが空くのを待ってからコネクションを返すか、または、取得失敗のエラーを返します(*2)。最大値を設定する理由として、データベースの設定やマシンの能力によって接続数の最大値が決まっているというのがあります。実際に接続する前に、コネクションプール側で接続を制限することで無用なエラーを避けることができます。また、複数のアプリケーション(例えばオンラインとバッチ処理など)で1つのデータベースを利用する場合、それぞれのコネクションプールで接続の上限を設定することで、限られた接続数を最適に割り当てることができます。
コネクションの最大値は、単に設定可能な上限を守るだけでなく、パフォーマンスチューニングの結果として調整されることがあります。例えば、大量のデータをソートする場合、同時に処理される上限を小さくした方がエラーを起こすことなく、スループットも向上することがあります。ソート処理では、データベース上の一時領域を使いますが、同時に多くの処理を行うと一時領域が足りなくなりデータベース上でエラーになる場合があります。
また、多数の処理を同時に行うよりも、同時に行う処理数を減らして1つ1つ処理した方がスループットが向上する場合が多いのです。最大値は処理の重さやサーバーの性能によって変わってくるので、負荷テストを行いながら最適値をチューニングする必要があります。
*2:最大値を超えて取得しようとした場合、一定時間コネクションオブジェクトが空くのを待ってコネクションオブジェクトを返すのか、すぐに取得失敗のエラーを返すのかは、コネクションプールの実装や設定によって異なります。
Object Poolの利用シーン
データベースのコネクションプールを例にObject Poolの必要性とメリットを説明しました。このほかに、Webコンテナにおけるサーブレットのスレッド自体もスレッドプールというObject Poolが利用されています。また、EJBコンテナでは、仕様としてEJBオブジェクトをプールして再利用するObject Poolがあります。さらにDIコンテナでは、多数のオブジェクトの利用を管理する際に、Object Poolが実装されています。
Object Poolの利用は、こうしたコンテナやフレームワークだけでなく、独自のアプリケーションでも必要になる場面が結構あります。例えば大量にメールを配信する処理を行いたい場合などでは、メールを配信するオブジェクトでObject Poolを利用することで、スループットの向上やネットワークの帯域、接続数などを考慮することができます。それ以外にも、ログファイルなどの大量のファイルアクセスを行う場合には、ファイルアクセスを行うオブジェクトをObject Poolにした方が、OS上の同時ファイルアクセス数やディスクへの入出力を考慮してチューニングすることができます。
Object Poolは、メモリやディスク、ネットワークなどの限られたリソースを利用する処理で利用されることが多いため、Resource Poolというパターンで説明されている場合もあります(*3)。
*3:Resource Poolパターンは、「Data Access Patterns」(Nock Clifton. Boston:Addison-Wesley)という書籍で紹介されています。
Object Poolパターンの概要
図2にObject Poolパターンの一般的な構造を示しました。また、下表にObject Poolの目的、問題、構成要素、関連するパターンを整理しておきました。
・目的
オブジェクトの生成にコストがかかる場合などで、オブジェクトの数を制限して再利用を管理する。
・問題
特定の規則に沿った、オブジェクトの生成を管理する。オブジェクトの数や、オブジェクトの取得と利用、利用が終了したオブジェクトの再利用などについて管理が必要となる。
・構成要素
Reusable Poolは、Clientによって利用されるReusableオブジェクトの利用を管理する。Clientは、Reusable Poolからオブジェクトを取得して、利用する。Reusable PoolによってすべてのReusableオブジェクトが保持されているため、統一した方法で管理できる。
・関連するパターン
Singleton
ReusablePool自体は通常Singletonパターンでオブジェクトを1つに制限する。
Factory Method
オブジェクトの生成では、Factory Methodが利用されることが多い。Factory Methodでは、生成のみを管理しその後のオブジェクトの管理はPoolが行う。
Clientは、Reusableオブジェクトが必要となったときに、Reusable PoolのacuireObjectを呼び出し、Reusableオブジェクトを取得します。acuireObjectメソッドは、プールから利用可能な(未使用の)オブジェクトを割り当てて返し、オブジェクトを利用中としてマークします。プールに利用可能なオブジェクトがない場合、新たにインスタンスを生成して返します。オブジェクト数が上限に達していて生成できない場合、オブジェクトがプールに返されるまで待つか、または、取得失敗のエラーを返します。
Clientは、Reusableオブジェクトが不必要になった(利用が終了した)ときに、Reusable PoolのreleaseObjectにReusableオブジェクトを引数として呼び出し、Reusableオブジェクトを開放します。releaseObjectメソッドでは、プールにオブジェクトを戻し、利用可能な状態にします。releaseObjectメソッドに相当する開放メソッドを、Reusableオブジェクト自身に実装する方法もあります(コネクションプールの場合のConnection#close()メソッドがこれに相当します)。
Object Poolの実装
Object Poolを実際に利用するには、独自に実装する方法もありますが、汎用的に利用できるObject Poolを提供するライブラリが存在します。ここでは、オープンソースのApacheプロジェクトのcommons-poolの実装とその利用方法を紹介します。
commons-poolは、Object Poolを実装するためのインターフェースと標準的な実装クラスのいくつかを用意しています。Object Poolインターフェースは2ページ目の図2(http://thinkit.jp/article/934/2/)のReusableクラスに相当するクラスを実装するためのインターフェースです。commons-poolでは、Object Poolの実装がいくつか用意されていますが、もっとも簡単に利用できる実装として、GenericObjectPoolクラスが提供されています。プールからオブジェクトを取得するためのメソッドがborrowObjectです。プールにオブジェクトを戻すためのメソッドがreturnObjectです。オブジェクトプールの利用を終了してプールにあるオブジェクトをすべて消去するためのメソッドがcloseです。GenericObjectPoolクラスは、Singletonにはなっていませんが、Singletonパターンを適用して、GenericObjectPoolのインスタンスを1つに制限することで、図2の場合と同様なオブジェクトプールにすることも可能です。
commons-poolでは、プールするためのオブジェクトを生成する部分でFactory Methodパターン(*4)も利用しています。PoolableObjectfactoryは、プールするオブジェクトを生成するためのFactoryクラスを実装するためのインターフェースです。commons-poolを利用する場合、PoolableObjectFactoryを実装して、プールするオブジェクトを生成するクラスを作成します。図3では、例としてプールするオブジェクトをPooledObject、PooledObjectを生成するFactoryクラスをPooledObjectFactoryという名称にしてあります。PoolableObjectFactoryのFactoryメソッド(オブジェクトを生成して返すメソッド)は、makeObjectです。そのほかにオブジェクトを利用状態にするactivateObjectメソッド、オブジェクトを利用可能(未使用状態)にするpassivateObjcetメソッド、オブジェクトを消去するためのdestoryObjectメソッドなどが定義されています。
*4:Factory Methodパターン:GoFの23のパターンで紹介されているデザインパターンです。
Object PoolパターンとFactory Methodパターンの組み合わせ
もっともシンプルにCommons-Poolを利用するには、図3において、PooledObjectとPooledObjectFactoryに相当するクラスを作成し、GenericObjectPoolを生成した後にsetFactoryメソッドで、作成したPooledObjectFactoryを渡します。Client、またはborrowObjectメソッドの内部からaddObjectメソッドが呼ばれると、PooledObjectFactoryを利用して、オブジェクトを生成して利用します。Factoryパターンと組み合わせたことで、commons-poosが提供する既存のGenericObject Poolを使って、ユーザーが定義したクラスをオブジェクトプールにすることができます。
このように、Object Poolパターンは、Factory Methodパターンと組み合わせて利用されることで、汎用的に利用できるオブジェクトプールを提供できます。
【参考文献】
アランシャロウェイ、ジェームズ.R.とロット(原著),村上 雅章(翻訳)『オブジェクト指向のこころ』ピアソン・エデュケーション(発行年:2005)
「Apache Commons - commons pool」
http://commons.apache.org/pool/
(アクセス:2009/05)