レベル: 中級 Martin Streicher (martin.streicher@gmail.com), Editor-in-Chief, McClatchy Interactive
2008年 1月 15日 エンタープライズ用として活用可能な検索エンジンである Apache Software Foundation の Solr と PHP アプリケーションを組み合わせるための方法を学びましょう。
私は「PHP でカスタム検索エンジンを作成する」の中で、PHP とオープンソースの Sphinx 検索エンジンを組み合わせ、LIKE (MySQL の場合には MATCH ) などに代わる、テキスト中心のデータベース・クエリーのための驚くほど高速な機能を作成しました (Sphinx に関連する情報については「参考文献」を参照)。
Sphinx はインストールや維持管理が容易であり、また非常に高機能です。しかも Sphinx の最近のリリースでは、ネイティブの MySQL エンジンを提供しており、Sphinx デーモンを別途実行する必要がありません。V0.9.8 (この記事の執筆時点での最新リリース) では、指定されたロケーションからの距離でカバーされるレコードを発見する geodistance クエリーと、複数のクエリーと結果セットを 1 つのネットワーク接続にまとめて最適化を行う、マルチ・クエリーという名前の機能が追加されています。
Sphinx は時間と共に改善が続けられており、ショッピング・サイトやブログ、その他数多くのアプリケーションに理想的なものになっています。Sphinx のサイトによれば、1 つのアプリケーションが今や 7 億の文書、または約 1.2 テラバイトのデータを索引付けすることができます。私は迷わず Sphinx をお薦めします。
しかし Sphinx は、アプリケーションやサイトの人気が高まり、利用数が増えるにつれて求められる、あるいは提供が必要となる、いくつかの機能をまだサポートしていません。特に、Sphinx はまだ索引を自動的に複製したり配布したりする機能がないため、Sphinx のデーモンが単一障害点になっています。(この回避策として、数台のマシンが同じデータベースに索引を付けるようにし、そうしたシステムをクラスター化する方法があります。) Sphinx は (Google がキャッシュしたページを表示する際に強調するように) 検索結果を強調表示することはなく、最近の検索結果を保持したりキャッシュしたりすることはなく、また正規表現 (regex) も日付に基づく操作もサポートしていません。
こうした機能が必要な場合には、あるいはエンタープライズ・グレードのソリューションを即座に求める場合には、Apache Software Foundation の Solr プロジェクトを検討してみてください。Solr は Lucene 検索エンジンをベースに、寛大な Apache Software License の条件でオープンソースとして提供されており、 (Lucene のサイトによれば)「Lucene の Java™ 検索ライブラリーをベースとするオープンソースのエンタープライズ検索サーバーであり、XML/HTTP と JSON の API、ヒットの強調表示、ファセット検索、キャッシング、複製、そして Web 管理インターフェースを備えています。」
何よりも注目すべきこととして、非常にトラフィックの多い Web サイトである Netflix、Digg、そして CNETの News.com や CNET Reviews は、Solr を使って強力な検索を行っています。Solr のウィキには、Solr を利用している公開サイトの長いリストが用意されています (「参考文献」を参照)。
ここでは Solr と PHP を組み合わせ、自動車部品のデータベースを検索する小さなアプリケーションを作成する方法を学びましょう。サンプルのデータベースはほんのわずかのレコードしか含んでいませんが、何百万というレコードを含めることも簡単にできます。この記事で使用しているソース・コードは、すべて「ダウンロード」セクションから入手することができます。
Solr をインストールする
Solr と PHP とを組み合わせるためには、Solr をインストールし、索引を設計し、Solr によって索引付けするデータを用意し、その索引をロードし、クエリーを実行するための PHP コードを作成し、そしてその結果を表示する必要があります。検索可能な索引を作成するために必要な作業の大部分は、コマンドラインから実行することができます。当然ですが、Solr 用の PHP のプログラム・インターフェースも索引の内容に影響を与えます。
Solr はJava 技術で実装されています。Solr と Solr の管理ツールを実行するためには、Java V1.5 の SDK (software development kit) である Java 5 SDK をインストールする必要があります。何社かのベンダーが Java V1.5 SDK を提供しており (例えば Sun Microsystems や IBM®
、 BEA Systems など)、どの実装を使っても Solr を活用することができます。そこで、オペレーティング・システムに適した Java パッケージを単純に選択し、完全なインストールを行うための指示に従います。
多くの場合、Java V1.5 のインストールは、自己解凍のアーカイブを実行して使用許諾条件を受け入れるのみ、という単純なものです。アーカイブの中のスクリプトが、面倒な作業のすべてをあっという間に行ってくれます。他のオペレーティング・システム (Debian など) では、APT リポジトリーの中で Java 5 SDKを提供しています。例えば Debian または Ubuntu を使用する場合であれば、sudo apt-get install sun-java5-jdk というコマンドで Java V1.5 ソフトウェアをインストールすることができます。
便利なことに、APT は Java 5 SDK を使うために必要なすべての依存ファイルも自動的にダウンロードします。
もし Java ソフトウェアが既にインストールされており、Java 実行可能ファイルが PATH にあるならば、java -version を実行して、どの Java コードが入っているのかを確認します。
ここでは、Mac OS X V10.5 Leopard オペレーティング・システムをデモのベースとして使います。Apple の Leopard には Java V1.5 が含まれています。Apache のデフォルト構成を少し変更するだけで、Leopard は PHP アプリケーションも実行することができます。Leopard のターミナル・ウィンドウで java -version を実行すると、以下の結果が得られます。
リスト 1. Leopard のターミナル・ウィンドウで java -version を実行する
$ which java
/usr/bin/java
$ java -version
java version "1.5.0_13"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_13-b05-237)
Java HotSpot(TM) Client VM (build 1.5.0_13-119, mixed mode, sharing)
|
注意: Leopard では、/Applications/Utilities/Java の Java Preferences アプリケーションの中で Java V1.4 と V1.5 とを切り替えることができます。もし Leopard システムが V1.4 とレポートしたら、Java Preferences を開き、設定を図 1 のように変更します。
図 1. Leopard の Java Preferences アプリケーション
Solr をインストールするためには、Apache.org のサイトに行き、Resources > Download をクリックし、プロジェクトの適当なミラー・サイトを選択し、そして表示されるフォルダー内をナビゲートして Solr V1.2 の tarball (.tgz ファイル) を選択します。ダウンロードを行うと、apache-solr-1.2.0.tgz のような名前のファイルが転送されます。この tarball を以下のコードを使って解凍します。
リスト 2. tarball を解凍する
$ tar xzf apache-solr-1.2.0.tgz
$ ls -F apache-solr-1.2.0
CHANGES.txt NOTICE.txt dist/ lib/
KEYS.txt README.txt docs/ src/
LICENSE.txt build.xml example/
|
新しく作成されたディレクトリーの中の dist という名前のフォルダーが、JAR (Java archive) としてバンドルされた Solr コードを含んでいます。サブディレクトリー example/exampledocs は、(通常は XML コードとして) フォーマットされた、Solr が索引付けできるデータのサンプルを含んでいます。
example ディレクトリーには、サンプル用の完全な Solr アプリケーションが含まれています。このアプリケーションを実行するためには、このアプリケーションのアーカイブ (start.jar) を使って単純に Java エンジンを起動するだけです。
リスト 3. Java エンジンを起動する
$ java -jar start.jar
2007-11-10 15:00:16.672::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
2007-11-10 15:00:16.866::INFO: jetty-6.1.3
...
INFO: SolrUpdateServlet.init() done
2007-11-10 15:00:18.694::INFO: Started SocketConnector @ 0.0.0.0:8983
|
これで、アプリケーションをポート 8983 で利用することができます。ブラウザーを起動し、アドレス・バーに
http://localhost:8983/solr/admin/ と入力します。これが Solr を管理するためのインターフェースです。(Solr サーバーを停止するにはコマンドラインで Ctrl+C を使います。)
しかし Solr の索引には、管理したり照会したりするためのデータが、(まだ) ありません。
Solr にデータをロードする
Solr は、そのままの状態でも驚くほど柔軟であり、効果的な索引を作成するためのさまざまなデータ型やルールをサポートしています。サポートされているものは非常に広範囲にわたりますが、もし標準コンポーネントでは不十分な場合には、新しい Java クラスを作成することで、Solr をさらにカスタマイズすることができます。
データ型とルールを指定すると、データを記述するための、また索引の構成方法を指定するための Solr スキーマを作成することができます。そうすると、そのスキーマに適合するデータをエクスポートすることができ、またデータを Solr にロードすることができます。Solr はすぐに索引を作成し、レコードが作成、変更、あるいは削除されると、即座に各索引を更新します。
Solr のデフォルト・スキーマは、Solr のソース・コード・リポジトリーの一部として Apache.org に用意されています。参考のために、デフォルトのスキーマのスニペットを以下に示します。
リスト 3. Solr のデフォルト・スキーマのスニペット
<schema name="example" version="1.1">
...
<fields>
<field name="id" type="string" indexed="true" stored="true" required="true" />
<field name="name" type="text" indexed="true" stored="true"/>
<field name="nameSort" type="string" indexed="true" stored="false"/>
<field name="cat" type="text" indexed="true" stored="true" multiValued="true"/>
...
</fields>
<uniqueKey>id</uniqueKey>
...
<copyField source="name" dest="nameSort"/>
...
</schema>
|
このスキーマの大部分は自明ですが、いくつかの点を説明しておく必要があります。
リスト 4 はファイル example/exampledocs/ipod_other.xml を示しています。このファイルは iPod のアクセサリー・カタログの 2 つのエントリーを示しています。
リスト 4. Solr のデフォルトの索引スキーマに対してフォーマットされたデータ
<add>
<doc>
<field name="id">F8V7067-APL-KIT</field>
<field name="name">Belkin Mobile Power Cord for iPod w/ Dock</field>
<field name="manu">Belkin</field>
<field name="cat">electronics</field>
<field name="cat">connector</field>
<field name="features">car power adapter, white</field>
<field name="weight">4</field>
<field name="price">19.95</field>
<field name="popularity">1</field>
<field name="inStock">false</field>
</doc>
<doc>
<field name="id">IW-02</field>
<field name="name">iPod & iPod Mini USB 2.0 Cable</field>
<field name="manu">Belkin</field>
<field name="cat">electronics</field>
<field name="cat">connector</field>
<field name="features">car power adapter for iPod, white</field>
<field name="weight">2</field>
<field name="price">11.50</field>
<field name="popularity">1</field>
<field name="inStock">false</field>
</doc>
</add>
|
add 要素は、エンベロープに入れられたレコードを索引に追加するための Solr のコマンドです。各レコードは、一連の名前付き field 要素を使ってフィールド値を指定する doc 要素の中に取り込まれます。フィールド weight、price、inStock、manu、features、そして popularity も、Solr のデフォルトの索引スキーマの中で定義されるフィールドです。features フィールドは cat と同じ属性を持っていますが、データが持つ意味合いは異なります。このフィールドは、ある製品の機能を (場合によると多数) 列挙します。
自動車の部品を検索する
この例では、自動車部品の集合に索引を付けします。各部品はいくつかのフィールドを持ち、そのうちの最も重要なフィールドの例を表 1 に示します。最初の列にはフィールドの名前が記載されています。2 列目には簡単な説明があり、3 列目はフィールドの論理型を記載しています。4 列目は、そのデータを表現するために使われる (リスト 5 のスキーマで定義される) 索引型を示しています。
表 1. 自動車部品のレコードのフィールド
| フィールド名 | 説明 | 型 | Solr の型 |
|---|
| Part number (一意 (unique)、必須 (mandatory)) | 識別番号 | String |
partno
| | Name | 簡単な説明 | String |
name
| | Model (必要 (required)、複数値 (multi-value)) | モデル名、例えば「Camaro」など | String |
model
| | 年式 (複数値) | モデル年式、例えば 2001 など | String |
year
| | Price | 単価 | Float |
price
| | In stock | 在庫の有無 | Boolean |
inStock
| | Features | その部品の機能 | String |
features
| | Timestamp | その部品の取扱記録 | String |
timestamp
| | Weight | 出荷重量 | Float |
weight
|
リスト 5 は、自動車部品の索引に使われる Solr スキーマの一部を示しています。このスキーマは主に Solr のデフォルト・スキーマをベースにしています。使用される特定のフィールド (名前のフィールドと属性のフィールド) は、(リスト 3 を見るとわかるように) デフォルトの中にある field 要素を単純に置き換えています。
リスト 5. 自動車部品の索引スキーマ
<?xml version="1.0" encoding="utf-8" ?>
<schema name="autoparts" version="1.0">
...
<fields>
<field name="partno" type="string" indexed="true"
stored="true" required="true" />
<field name="name" type="text" indexed="true"
stored="true" required="true" />
<field name="model" type="text_ws" indexed="true" stored="true"
multiValued="true" required="true" />
<field name="year" type="text_ws" indexed="true" stored="true"
multiValued="true" omitNorms="true" />
<field name="price" type="sfloat" indexed="true"
stored="true" required="true" />
<field name="inStock" type="boolean" indexed="true"
stored="true" default="false" />
<field name="features" type="text" indexed="true"
stored="true" multiValued="true" />
<field name="timestamp" type="date" indexed="true"
stored="true" default="NOW" multiValued="false" />
<field name="weight" type="sfloat" indexed="true" stored="true" />
</fields>
<uniqueKey>partno</uniqueKey>
<defaultSearchField>name</defaultSearchField>
</schema>
|
上記のようにフィールドが指定されると、Solr にアップロードするためにエクスポートされ、整形された自動車部品のデータベースは、リスト 6 のようなものになります。
リスト 6. 索引用に整形された自動車部品のデータベース
<add>
<doc>
<field name="partno">1</field>
<field name="name">Spark plug</field>
<field name="model">Boxster</field>
<field name="model">924</field>
<field name="year">1999</field>
<field name="year">2000</field>
<field name="price">25.00</field>
<field name="inStock">true</field>
</doc>
<doc>
<field name="partno">2</field>
<field name="name">Windshield</field>
<field name="model">911</field>
<field name="year">1991</field>
<field name="year">1999</field>
<field name="price">15.00</field>
<field name="inStock">false</field>
</doc>
</add>
|
この新しい索引スキーマをインストールし、Solr にデータをロードしましょう。まず、もしまだ Solr デーモンが実行していたら、Ctrl+C を使ってそれを停止します。既存の Solr スキーマのアーカイブを example/solr/conf/schema.xml の中に作ります。次に、リスト 6 からテキスト・ファイルを作成し、それを /tmp/schema.xml に保存し、それを example/solr/conf/schema.xml にコピーします。リスト 7 に示すデータ用に別のファイルを作成します。今度は Solr を再度起動し、このサンプルに提供されているポスト用のユーティリティーを使います。
リスト 7. 新しいスキーマを使って Solr を起動する
$ cd apache-solr-1.2/example
$ cp solr/conf/schema.xml solr/conf/default_schema.xml
$ chmod a-w solr/conf/default_schema.xml
$ vi /tmp/schema.xml
...
$ cp /tmp/schema.xml solr/conf/schema.xml
$ vi /tmp/parts.xml
...
$ java -jar start.jar
...
2007-11-11 16:56:48.279::INFO: Started SocketConnector @ 0.0.0.0:8983
$ java -jar exampledocs/post.jar /tmp/parts.xml
SimplePostTool: version 1.2
SimplePostTool: WARNING: Make sure your XML documents are encoded in UTF-8,
other encodings are not currently supported
SimplePostTool: POSTing files to http://localhost:8983/solr/update...
SimplePostTool: POSTing file parts.xml
SimplePostTool: COMMITting Solr index changes...
|
成功です。この索引が存在し、2 つの文書を含んでいることを確認したい場合には、ブラウザーで再度 http://localhost:8983/solr/admin/ にアクセスします。ページの先頭に「(autoparts)」と表示されるはずです。表示されているようなら、ページの中ほどにあるクエリー・ボックスをクリックし、partno: 1 or partno: 2 と入力します。
結果は以下のようになるはずです。
3 on 10 0 partno: 1 OR partno: 2 2.2
true Boxster 924 Spark plug 1 25.0 2007-11-11T21:58:45.899Z 1999 2000
false 911 Windshield 2 15.0 2007-11-11T21:58:45.953Z 1991 1999
|
いくつか他のクエリーも試してみてください。Lucene のクエリー (Solr の中の検索エンジン) の構文は Lucene のウィキに説明されています (「参考文献」を参照)。
また、再度データを編集し、ロードしてみる必要もあります。partno フィールドは一意である (unique) と宣言されているため、同じ部品番号に対してアップロード操作を繰り返しても、古い索引レコードが新しいレコードと置き換えられるにすぎません。add コマンドの他に、commit、optimize、delete を使うことができます。最後のコマンドは、ID によって特定のレコードを削除し、あるいはクエリーによって多数のレコードを削除します。
今度は PHP の出番です
いよいよ、このサンプルの中に PHP が登場します。
Solr には少なくとも 2 つの PHP 用の API があります。最も堅牢な実装は、Donovan Jimenez による PHP Solr Client (「参考文献」を参照) です。このコードは Solr と同じ条件でライセンスされており、充実したドキュメンテーションを備え、Solr V1.2 と互換性があります。この記事の執筆時点での最新リリースの日付は 2007年10月2日です。
Solr Client は以下の 4 つの PHP クラスを提供しています。
-
Apache_Solr_Service は Solr サーバーに関する内容を表します。このクラスのメソッドを使って、サーバーに対して ping を実行したり、文書の追加や削除、変更のコミット、索引の最適化などを行ったり、クエリーを実行したりします。
-
Apache_Solr_Document は Solr 文書の実際を管理します。このクラスのメソッドは、(キーと値の) ペアと、複数の値を持つフィールドを管理します。フィールドの値は、直接、逆参照することによってアクセスされます (例えば $document->title = 'Something'; ... echo $document->title; など)。
-
Apache_Solr_Response は Solr のレスポンスをカプセル化します。このコードは json_decode() 関数に依存しています。この関数は PHP V5.2.0 以降にバンドルされていますが、PECL (PHP Extension Community Library、「参考文献」を参照) からインストールすることもできます。
-
Apache_Solr_Service_Balancer は Apache_Solr_Service を機能強化しており、このクラスを利用すると、あるディストリビューションの複数の Solr サービスに接続することができます。この記事では、このクラスに関しては説明しません。
PHP Solr Client をダウンロードして (「参考文献」を参照)、作業ディレクトリーに解凍し、ディレクトリー名を SolrPhpClient に変更します。次に、ファイル Apache/Solr/Service.php をチェックします。この記事の執筆時点では、335 行目に終了用のセミコロンが抜けていました。必要であれば、このファイルを編集してセミコロンを追加します。またファイル Apache/Solr/Document.php もチェックします。112 行目から 117 行目は以下のようになっているはずです。
if (!is_array($this->_fields[$key]))
{
$this->_fields[$key] = array($this->_fields[$key]);
}
$this->_fields[$key][] = $value;
|
これらのファイルを修正すると、他の PHP ライブラリーと並べて Apache ディレクトリーをインストールすることができます。
以下のコードの PHP アプリケーションは、Solr サービスに接続し、索引に 2 つの文書を追加し、そして先ほど使用した部品番号のクエリーを実行します。
リスト 8. Solr 索引に接続し、その Solr 索引をロードし、その Solr 索引にクエリーを実行するサンプル PHP アプリケーション
<?php
require_once( 'Apache/Solr/Service.php' );
//
//
// Try to connect to the named server, port, and url
//
$solr = new Apache_Solr_Service( 'localhost', '8983', '/solr' );
if ( ! $solr->ping() ) {
echo 'Solr service not responding.';
exit;
}
//
//
// Create two documents to represent two auto parts.
// In practice, documents would likely be assembled from a
// database query.
//
$parts = array(
'spark_plug' => array(
'partno' => 1,
'name' => 'Spark plug',
'model' => array( 'Boxster', '924' ),
'year' => array( 1999, 2000 ),
'price' => 25.00,
'inStock' => true,
),
'windshield' => array(
'partno' => 2,
'name' => 'Windshield',
'model' => '911',
'year' => array( 1999, 2000 ),
'price' => 15.00,
'inStock' => false,
)
);
$documents = array();
foreach ( $parts as $item => $fields ) {
$part = new Apache_Solr_Document();
foreach ( $fields as $key => $value ) {
if ( is_array( $value ) ) {
foreach ( $value as $datum ) {
$part->setMultiValue( $key, $datum );
}
}
else {
$part->$key = $value;
}
}
$documents[] = $part;
}
//
//
// Load the documents into the index
//
try {
$solr->addDocuments( $documents );
$solr->commit();
$solr->optimize();
}
catch ( Exception $e ) {
echo $e->getMessage();
}
//
//
// Run some queries. Provide the raw path, a starting offset
// for result documents, and the maximum number of result
// documents to return. You can also use a fourth parameter
// to control how results are sorted and highlighted,
// among other options.
//
$offset = 0;
$limit = 10;
$queries = array(
'partno: 1 OR partno: 2',
'model: Boxster',
'name: plug'
);
foreach ( $queries as $query ) {
$response = $solr->search( $query, $offset, $limit );
if ( $response->getHttpStatus() == 200 ) {
// print_r( $response->getRawResponse() );
if ( $response->response->numFound > 0 ) {
echo "$query <br />";
foreach ( $response->response->docs as $doc ) {
echo "$doc->partno $doc->name <br />";
}
echo '<br />';
}
}
else {
echo $response->getHttpStatusMessage();
}
}
?>
|
このコードはまず、指定されたポートとパスにある指定された Solr サーバーに接続し、サーバーが動作しているかどうかを ping() メソッドを使って確認します。
次にこのコードは、PHP の配列として表現されたレコードを Solr 文書に変換します。フィールドの値が 1 つの場合には、単純なアクセサーが (キーと値の) ペアをこの文書に追加します。フィールドが複数の値を持つ場合には、値のリストは特別な関数 setMultiValue() を持つキーに割り当てられられます。このプロセスは Solr 文書を XML で表現する場合に非常に似ていることがわかると思います。
最適化をするために、addDocuments() は複数の文書を索引の中に挿入します。その後に行われる commit() 関数と optimize() 関数によって追加が完了します。
一番最後に、いくつかのクエリーが索引からデータを取得しています。この結果は 2 つの視点から見ることができます。getRawResponse() 関数は、構文解析されていない全体の結果を返します。一方 docs() 関数は名前付きアクセサーを使って文書の配列を返します。
もしクエリーが Solr から OK を取得できない場合には、このコードはエラー・メッセージを出力します。空の結果セットの場合は何も出力されません。
さらに強力なものにするために
Solr は信じられないほど強力であり、また PHP API のおかげで、どのようなプラットフォームとも容易に統合することができます。さらに良いことに、Solr はセットアップや操作が容易であり、必要に応じて高度な機能を有効にすることができます。そして何よりも良いことに、Solr は無料です。検索エンジンにお金を払う必要はありません。費用を節約し、Solr を活用してください。
Solr の Web サイトを調べ、ソートや、結果のカテゴリー分け、複製など、より高度な構成について学んでください。Lucene は Solr システムの基礎となっている検索技術であるため、Lucene の Web サイトも豊富な情報源です。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Sample PHP and Solr application | os-php-apachesolr.src.zip | 109KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
- Solr プロジェクトのミラー・サイトの 1 つから Solr をダウンロードしてください。
-
Sphinx 検索エンジンを詳しく学び、またこのエンジンをダウンロードしてください。
- Donovan Jimenez による PHP Solr Client をダウンロードしてください。
-
PECL リポジトリーを訪れてください。ここは最初に訪れるべき場所として、PHP の拡張機能をダウンロードし、開発するための既知の拡張機能やホスト機能をすべて用意しています。
- 皆さんのオープンソース開発プロジェクトを IBM trial software を使って革新してください。ダウンロード、または DVD で入手することができます。
-
IBM 製品の評価版をダウンロードし、DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などのアプリケーション開発ツールやミドルウェア製品を試してみてください。
議論するために
著者について  | |  | Martin Streicherは McClatchy Interactive の最高技術責任者であり、Linux Magazineの編集長であり、Web 開発者であり、また developerWorks への頻繁な寄稿者でもあります。彼は Purdue Universityでコンピューター・サイエンスの修士号を取得しており、1986年以来、UNIX ライクのシステムでプログラミングを行ってきています。 |
記事の評価
|