レベル: 中級 安達 宜隆, IM&Lotus開発 / ソフトウェア開発研究所, IBM Japan
2009年 2月 13日 IBM OmniFind Enterprise Edition(以下OmniFind)は、製品として1ノード、2ノード、4ノード構成をサポートしています。検索システムの規模に応じて最適なノード構成を選択することが可能であり、様々な要件に柔軟に対応することができます。しかし、場合によっては、5ノード以上の構成にしたいことや、ノードの役割を柔軟に設定したいこともありえます。本連載では、OmniFindに付属のESSearchApplicationをカスタマイズすることによって、複数のOmniFindシステムを統合し、柔軟なノード構成に対応する方法を説明します。
OmniFindの概要
最初にOmniFindの全体構成を簡単に説明します(図1)。まず検索対象となるドキュメントをクローラーが収集します。次に、パーサーが構文解析・トークン分割などの処理を行い、解析後のデータをインデクサーに渡します。インデクサーは、検索時に使用するインデックス(文書を検索するための索引)を作成する処理を行います。
エンドユーザーが検索を行うときには、まず検索アプリケーション(ESSearchApplication)がエンドユーザーの検索クエリ(検索キーワード)を受け取ります。検索アプリケーションはSIAPIと呼ばれる検索用のAPIを利用して、検索クエリをランタイムに渡します。OmniFindでは、この検索アプリケーションとランタイム間の通信にはHTTPを利用しており、検索アプリケーションとランタイムを別ノードに分散させることも可能になっています。検索クエリを受け取ったランタイムはインデックスを捜査し、検索クエリに対応するドキュメントを探し出します。そして、見つかったドキュメントの一覧をHTTPのレスポンスとして検索アプリケーションに返します。こうして得られたドキュメント一覧が検索結果としてエンドユーザーに提示され、1回の検索処理が完了します。
図1 OmniFindの全体構成
OmniFindでは、クローラー、パーサー、インデクサー、ランタイムの組み合わせをコレクションと呼んでいます。コレクションは複数定義することが可能であり、コレクション毎に設定を変更することも可能です。このように複数のコレクションを利用することで、柔軟な運用が実現できるようになっています。ただし、複数のコレクションを定義した場合は、インデックスも複数作成されることになります。そのため、コレクションを横断して検索を行いたい場合は、検索クエリを複数のコレクションのランタイムに渡し、各コレクションから返ってきた検索結果を統合する処理が必要になります。この処理を行うコンポーネントはFederatorと呼ばれ、デフォルトでは、ランタイムと同じノードにあるRemoteFederatorが利用されています。RemoteFederatorは図2のようにランタイムと検索アプリケーションの間に入り、検索クエリを複数のコレクションのランタイムに渡し、複数の検索結果をまとめて検索アプリケーションに返す処理を行っています。
図2 コレクションとFederator
このように、OmniFindでは複数のコンポーネントが互いに連携することによって1つの検索システムを提供しているのですが、これらのコンポーネントを複数のノード(マシン)に分散させることもできます。インストール時に選択したノード構成により、各コンポーネントが分散されます。小規模なシステムであれば1ノード構成、検索対象のドキュメントが多かったり、エンドユーザーの数が多く対象の検索クエリを処理したりする場合は、2ノード構成や4ノード構成を選択することが可能です。
柔軟なノード構成への対応
OmniFindではインストール時に適切なノード数を指定するだけで、簡単にノード構成を設定できます。しかし、製品としてサポートしている1、2または4ノード構成だけでは対応できない場合もあります。
1例として、非常に大規模なシステムを構築する場合があげられます。OmniFindにおけるドキュメントの上限数に関しては、検索対象のドキュメントの種類やハードウェア、そしてOSなどの環境によって異なるため、明確な値はありませんが、メモリーモデルがラージの場合は、約2000万ドキュメントの検索を想定して各種設定が行われます。この場合、例えば1億ドキュメントの検索システムを構築する場合には、5つの検索サーバーが必要となります。このような大規模システムには、単純な4ノード構成では対応できず、5ノード以上の構成を柔軟にとれるようにしなければなりません。
また、ノード数が少ない場合でも、ネットワーク構成によって、ノードの役割を柔軟に設定したいという例も考えられます。例えば、データソースがWANで区切られた環境を考えます。この場合、全てのデータをWAN経由でやりとりすると時間がかかってしまいます。そこで、WANで区切られた拠点毎にOmniFindを導入し、WAN上を流れるデータは検索クエリと検索結果のみにすることで、ネットワークによるパフォーマンスの劣化を少なくすることができます。しかし、既存の2ノード構成や4ノード構成の場合、このような構成をとることはできないため、WANで区切られた環境に対応することは難しいと言わざるをえません。
ESSearchApplicationのカスタマイズによる複数システムの統合
前述したように、製品をインストールするだけでは、全ての要望にかなえられるような柔軟なノード構成をとることはできません。そこで、検索アプリケーションをカスタマイズすることによって、柔軟なノード構成を実現する手法を考えます。具体的には、別々にインストールされたOmniFindの検索結果を統合する機能を、検索アプリケーションに加えることにします。図2では検索サーバー上(ランタイムが存在するノード上)でRemoteFederatorが複数のコレクションの検索を統合していましたが、これを図3のように検索アプリケーション側で行うようにします。こうすることによって、OmniFindのシステムをまたいだ検索の統合が可能になり、柔軟なシステムを実現することが可能になります。
図3 検索アプリーケーション上のFederator
既存のOmniFind及びESSearchApplicationでは、図2のようにRemoteFederatorを利用して複数のコレクションの検索を統合しています。RemoteFederatorでは、検索アプリケーションとは別のノード上(Remote)で検索結果の統合処理が行われ、アプリケーション側(Local)では統合された検索しか見ることができません。今回のカスタマイズでは、この検索の統合処理をアプリケーション側で行うようにします。
前述したように、検索アプリケーションとランタイム間の通信はHTTPで行われているため、それぞれのコンポーネントは別のノード上に配置することができます。そこで、検索アプリケーションをカスタマイズし、別のノードにある複数のOmniFindシステムのランタイムに対して、検索クエリを同時に投げるようにします。そして、各OmniFindシステムから返ってきた検索結果を検索アプリケーション内で統合し、1つの検索結果にします。最終的に表示する検索結果はこの統合された検索結果になります。これらの処理は検索アプリケーション内で行われるため、エンドユーザーからは、あたかも1つの検索システムを使っているように見えます。
このように、検索クエリを複数のランタイムに投げ、かつ検索結果を統合する処理、すなわちRemoteFederatorが行っていた処理を、検索アプリケーション内で行う必要が出てくるのですが、幸いにも、OmniFindにはRemoteFederatorと対になる、LocalFederatorがAPIとして提供されています。LocalFederatorは、ランタイム側(Remote)ではなく検索アプリケーション側(Local)で検索処理を統合します。このLocalFederatorを利用して、複数のOmniFindシステムを統合する検索アプリケーションを開発することにします。
ESSearchApplicationのカスタマイズ
それでは、具体的なカスタマイズを行っていきます。まずは、設定ファイルです。ESSearchApplicationではWEB-INFディレクトリのconfig.propertiesでランタイムノードのホスト名(IPアドレス)、及びHTTP接続するためのusernameとpasswordを指定していますが、これを複数のランタイムに対して行うため、下記のようにデリミタ(";")をつけて指定することにします。
リスト1 config.properties
hostname=omnifind1.ibm.com;omnifind2.ibm.com
username=username1;username2
password=password1;password2
|
次に、この設定ファイルから読み込まれたjava.util.Propertiesから、OmniFindシステム毎に分割したPropertiesの配列を返すHelperメソッドを定義します。これは、FederatedSearchHelperという新しいクラスを作り、そのstaticメソッドとして定義することにします。
リスト2 FederatedSearchHelper.java
public class FederatedSearchHelper {
private final static String HOSTNAME_DELIMITER = ";";
public static Properties[] getProperties(Properties properties) {
// check properties for LocalFederator
String[] hostnames =
((String)properties.getProperty("hostname")).split(HOSTNAME_DELIMITER);
String[] usernames = null;
String propUsername = properties.getProperty("username");
if (propUsername != null &&
propUsername.split(HOSTNAME_DELIMITER).length == hostnames.length) {
usernames = propUsername.split(HOSTNAME_DELIMITER);
}
String[] passwords = null;
String propPassword = properties.getProperty("password");
if (propPassword != null &&
propPassword.split(HOSTNAME_DELIMITER).length == hostnames.length) {
passwords = propPassword.split(HOSTNAME_DELIMITER);
}
Properties[] props = new Properties[hostnames.length];
for (int i = 0 ; i < hostnames.length ; i++) {
props[i] = (Properties)properties.clone();
props[i].setProperty("hostname", hostnames[i]);
if (usernames != null) {
props[i].setProperty("username", usernames[i]);
}
if (passwords != null) {
props[i].setProperty("password", passwords[i]);
}
}
return props;
}
}
|
これで設定ファイルを読み込む準備は整いましたので、これを利用してLocalFederatorを使用するようにカスタマイズしていきます。ESSearchApplicationでは、SIAPIを利用したコードは、SiapiHelper.javaにまとめられていますので、このファイルを変更していきます。
まず、デフォルトでは1つのSearchService及びBrowseService利用するように設定されていますが、これを複数のインスタンスを利用するように変更します。
リスト3 SiapiHelper.java (1)
public class SiapiHelper {
private SearchService[] searchServices;
...
public void initialize(Properties properties) throws SiapiException, Exception {
...
Properties[] props = FederatedSearchHelper.getProperties(properties);
searchServices = new SearchService[props.length];
for (int i = 0 ; i < props.length ; i++) {
searchServices[i] = searchFactory.getSearchService(props[i]);
if (browseService == null) {
browseService =
browseFactory.getBrowseService(props[i]);
}
}
...
}
|
次に、RemoteFederatorを利用している部分を、LocalFederatorを利用するように変更します。まずfederatorの読み込みを行っているloadRemoteFederatorメソッドをloadLocalFederatorメソッドに変更します。loadRemoteFederatorを呼び出しているBaseActionの部分もloadLocalFederatorに置き換えます。また、getAvailableSources、getAvailableDocumentTypesそしてgetAvailableLanguage内で参照されているRemoteFederatorもLocalFederatorに変更します。
リスト4 SiapiHelper.java(2)
public void loadLocalFederator() throws SiapiException, Exception {
List searchableList = new ArrayList();
for (int i = 0 ; i < searchServices.length ; i++) {
Searchable[] searchables = searchServices[i].getAvailableSearchables(appInfo);
for (int j = 0 ; j < searchables.length ; j++) {
searchableList.add(searchables[j]);
}
}
Searchable[] searchSearchables =
(Searchable[])searchableList.toArray(new Searchable[searchableList.size()]);
federator = searchFactory.createLocalFederator(searchSearchables);
}
|
コレクションを取得するgetCollectionInfosメソッドもLocalFederator用に変更します。getScopesメソッドで呼ばれているfederator.getCollectionInfosは、このgetCollectionInfosを使うように変更します。
リスト5 SiapiHelper.java(3)
public CollectionInfo[] getCollectionInfos() throws SiapiException, Exception {
// return the list of collections
if (federator != null) {
List collectionList = new ArrayList();
Searchable[] searchables = federator.getSearchables();
for (int i = 0 ; i < searchables.length ; i++) {
collectionList.add(searchables[i].getCollectionInfo());
}
return (CollectionInfo[])collectionList.toArray(
new CollectionInfo[collectionList.size()]);
} else {
return null;
}
}
|
検索を行うsearchメソッドは、LocalFederatorに対してSearchableの配列を渡すように以下のようにします。
リスト6 SiapiHelper.java(4)
public ResultSet search(Query query, String[] collections)
throws SiapiException, Exception {
if (federator != null) {
if (collections != null && collections.length > 0) {
Searchable[] searchables = federator.getSearchables();
List searchableList = new ArrayList();
for (int i = 0 ; i < collections.length ; i++) {
for (int j = 0 ; j < searchables.length ; j++) {
if (collections[i].equals(searchables[j].getCollectionInfo().getID())) {
if (!searchableList.contains(searchables[j])) {
searchableList.add(searchables[j]);
}
break;
}
}
}
Searchable[] searchSearchables =
(Searchable[])searchableList.toArray(new Searchable[searchableList.size()]);
return federator.search(query, searchSearchables);
} else {
return federator.search(query);
}
} else {
return null;
}
}
|
以上で、アプリケーションのカスタマイズは終了です。config.propertiesに適切なホスト名を指定することで、複数のOmniFindにまたがった検索を行うことが可能になります。
まとめ
LocalFederatorを利用するように検索アプリケーションをカスタマイズすることによって、複数のOmniFindシステムにまたがった検索を行うことが可能になりました。これによって、柔軟なノード構成を取ることが可能になり、より要件にあった検索システムを構築することが可能になります。
参考文献
著者について  | |  | 安達 宜隆は、ソフトウェア開発研究所のソフトウェア・エンジニアであり IBM OmniFind Enterprise Edition の開発・保守に携わっています。最近、専門以外の一般教養を身につけようと思い立ち、仕事とは全然関係ない公共政策を勉強してみました。しかしすぐに飽きてしまい、教材の途中で勉強をやめてしまいました。慣れないことはやるものじゃないとつくづく実感しました。 |
記事の評価
|