MySQL/PostgreSQL+Sennaで行うラクラク全文検索……Tritonn&Ludia導入のポイント

本記事は、WEB+DB PRESS Vol.42の特別企画「[最新版Senna対応]ゼロからはじめる検索プログラミング」の補足記事です。

Tritonn、Ludia、そしてSennaとは……

昨今のWeb 2.0と呼ばれるようなWebシステムでは、一般的に大量のコンテンツデータを内部に保有しているのではないでしょうか。大量のコンテンツから目的のコンテンツをユーザが選び取る手段の一つとして全文検索が挙げられます。全文検索とは、検索対象コンテンツの中身すべてに対して検索を行うことを指します。たとえば、タグやタイトルを対象にした検索だけでは、目的のコンテンツを発見できないような場合に有効な検索です。

データベースに保持された大量のデータを簡単に全文検索したいという場合も多いことでしょう。本稿では、それを実現にする全文検索システムとして、次の2つを取り上げて紹介します。

これらはそれぞれ、Tritonnは「MySQL⁠⁠、Ludiaは「PostgreSQL」という、Webシステムを開発する上で人気の高いDBMSで簡単に全文検索を行えるように改良したシステムです。

さて、TritonnとLudiaは、ともにSennaという全文検索エンジンを内部で利用しています。Sennaは、筆者が所属している(有)未来検索ブラジルを中心に開発されている、オープンソースの全文検索エンジンです。

Sennaは、それ単体で使用することができる全文検索システムです。単体で使用する場合には、SennaQLという問い合わせ言語を使用します。検索プログラムの基礎知識のほか、SennaQLの使用方法をはじめとしたSennaの詳細などは以下で解説していますので、合わせてぜひご参照ください。

  • WEB+DB PRESSVol.42⁠ 特別企画:[最新版Senna対応]ゼロからはじめる検索プログラミング

以降では、TritonnとLudiaについて、導入方法や使用方法、実用上の留意事項についてまとめて紹介します。

Tritonn

インストール

Tritonnプロジェクトが配布する改変済みMySQLのインストールについて説明します。Tritonnプロジェクトでは、改変済みのMySQLのバイナリ群をRPM形式とtar ball形式の両形式で配布しております。ここでは、RPM形式のパッケージをインストールする方法を説明します。

まず、MySQLのRPMパッケージがすでにインストールされていないかどうかを確認します。Tritonnは改造したMySQLですので、MySQLとパッケージの共存をすることができません。

 rpm -qa | grep -i mysql

上記コマンドを実行して、MySQL関連のRPMパッケージ名があった場合には、

 rpm -e [削除したいパッケージ名]

にてパッケージを削除してください。

次に、Tritonnが必要とするRPMファイル6つを、Tritonnのバイナリファイル配布サイトからダウンロードします。2007/12/10現在、以下の6つのファイルが必要となります。

  • MySQL-client-5.0.45-tritonn.1.0.7.i386.rpm
  • MySQL-server-5.0.45-tritonn.1.0.7.i386.rpm
  • MySQL-shared-5.0.45-tritonn.1.0.7.i386.rpm
  • mecab-0.96-tritonn.1.0.7.i386.rpm
  • mecab-ipadic-2.7.0.20070801-tritonn.1.0.7.i386.rpm
  • senna-1.0.9-tritonn.1.0.7.i386.rpm

上記ファイル群について、図1のコマンドを順番に発行してインストールを行います。各パッケージには依存関係がありますので、必ず以下の順番でのインストールを行ってください。

図1 パッケージのインストール手順
 rpm -i mecab-0.96-tritonn.1.0.7.i386.rpm
 rpm -i mecab-ipadic-2.7.0.20070801-tritonn.1.0.7.i386.rpm
 rpm -i senna-1.0.9-tritonn.1.0.7.i386.rpm
 rpm -i MySQL-shared-5.0.45-tritonn.1.0.7.i386.rpm
 rpm -i MySQL-client-5.0.45-tritonn.1.0.7.i386.rpm
 rpm -i MySQL-server-5.0.45-tritonn.1.0.7.i386.rpm

図1の実行で、RPMファイルに署名がない旨のエラーが発生する場合には「--nogpgcheck」というオプションを指定し、署名のチェックを無効化してください。

tar ballパッケージからのインストールや、ソースコードからのインストール方法は、Tritonnプロジェクトのインストール解説ページを参照ください。

クエリ例

改変済みのMySQLは、リスト1のようなクエリで全文検索インデックスを作成することができます。

リスト1 全文検索インデックスの作成(Tritonn)
●テーブル作成時の全文検索インデックス付与
 CREATE TABLE tbl1 (
   id INTEGER AUTO_INCREMENT,
   PRIMARY KEY (id),
   text TEXT NOT NULL,
   FULLTEXT INDEX USING NGRAM (text)
 );

●既存のテーブルtbl1のカラムtextへの全文検索インデックス付与
 ALTER TABLE tbl1 ADD FULLTEXT INDEX ft USING NGRAM (text);

リスト1のクエリにて、テーブルtbl1のカラムtextに全文検索インデックスが付与されました。以下のクエリで全文検索を行うことができます。

 SELECT * FROM tbl1 WHERE MATCH(text) AGAINST('検索クエリ');

また、Yahoo!やGoogleのように、AND検索やOR検索などをサポートする場合には以下のようなクエリを実行します。

 SELECT * FROM tbl1 WHERE MATCH(text) AGAINST('検索クエリ' IN BOOLEAN MODE);

IN BOOLEAN MODEを指定して、検索クエリ内にスペースで区切られた複数の単語が存在したとします。この場合、Yahoo!やGoogleではAND検索が行われますが、TritonnではOR検索が行われます。デフォルトでAND検索を行うためには、以下のように検索クエリの頭に「*D+」を付与してください。

 SELECT * FROM tbl1 WHERE MATCH(text) AGAINST('*D+ 検索クエリ' IN BOOLEAN MODE);

もちろん、他カラムを用いての絞込みやソートを行うことが可能です。

 SELECT * FROM tbl1 WHERE MATCH(text) AGAINST('*D+ 検索クエリ' IN BOOLEAN MODE) AND id < 1000 ORDER BY id;

また、検索結果のスコアも取得することができます。MATCH~AGAINSTの部分を関数のように扱うことにより、スコアを取得することができます。

 SELECT id, MATCH(text) AGAINST ('*D+ 検索クエリ') AS score FROM tbl1 WHERE MATCH(text) AGAINST('*D+ 検索クエリ' IN BOOLEAN MODE);

Tritonn使用上のTips

Tritonnを使用する上で、知っておくとうれしい(!?)事項を4つ紹介いたします。

1)インデックス利用の注意点と2ind機能

Tritonnでは、データベースのインデックスを利用する際に留意することがあります。MySQLでは、EXPLAINコマンドを利用することによって、インデックスがどのように利用されているかを確認することができます。しかし、Tritonnで付与された全文検索インデックスが利用されているかどうかは、EXPLAINコマンドでは現状わかりません。よって、全文検索句であるMATCH AGAINSTを除いたクエリをEXPLAINコマンドで解析し、インデックスが利用されていることを確認しましょう。

そして、全文検索インデックスとEXPLAINコマンドで使われていることが確認されたインデックスとを組み合わせるためには、Tritonnの「2ind」と呼ばれる機能を有効にすることが必要となります。

MySQLの設定ファイルであるmy.cnfの[mysqld]というセクションに以下の1行を追加することによって、2ind機能を有効にすることができます。

 senna_2ind

また、MySQLへの接続ごとに、以下のクエリを実行することによっても2ind機能を有効にすることができます。

 SET SESSION senna_2ind=ON;

2)検索応答のスループット

クライアントの数が多くなるにつれ、検索応答のスループットが落ち込みます。そのような場合には、複数台のサーバ上でTritonnを動かし、ユーザからの問い合わせを振り分ける必要があります。Tritonnはレプリケーションに対応しているため、レプリケーションのスレーブ側のサーバを複数台用意することをお勧めいたします。

3)検索結果の取得の処理

検索結果のすべてを取得する処理は大変時間がかかります。SELECT文においてLIMITを指定することによって、必要な検索結果のみを取得しましょう。その際に、検索結果の総件数を得るためにはSQL_CALC_FOUND_ROWSオプションとFOUND_ROWS()という関数を利用することができます。詳しくは、MySQLのリファレンスマニュアルをご覧ください。

4)kwic()関数

kwic()関数を利用することによって、Webの検索エンジンのように、コンテンツ中の検索語の前後にHTMLタグ等を付与して目立たせることができるようになります。詳細は、Tritonnの公式ページにあるドキュメントをご覧ください。

Ludia

インストール

Ludiaのインストール方法について説明します。MeCabとSenna、そしてPostgreSQL 8.1/8.2のいずれかのバージョンがインストールされていることを前提とします。

まず、Ludiaのアーカイブを展開し、以下の要領でインストールを行います。

 ./configure
 make
 make install//root権限で

pg_configコマンドやsenna_cfgコマンドのインストール先が環境変数PATHに指定されていない場合は、configure時に明示的な指定が必要となります。たとえば、pg_configが/usr/local/pgsql/bin/pg_configに存在し、senna_cfgが/usr/local/bin/senna-cfgに存在する場合には、以下のようなオプションを指定してconfigureを実行します。

 ./configure --with-pg-config=/usr/local/pgsql/bin/pg_config \
              --with-senna-cfg=/usr/local/bin/senna-cfg

次に、Ludiaを使用するデータベースにおいて、インデックスアクセスメソッドと呼ばれるものを定義します。たとえば、データベースtdbに対して定義するには、以下のようなコマンドを実行します。

 psql -f /usr/local/pgsql/share/pgsenna2.sql tdb

pgsenna2.sqlはPostgreSQLのshareディレクトリにインストール されています。

クエリ例

Ludiaでは、リスト2のようなクエリで全文検索インデックスを作成することができます。

リスト2 全文検索インデックスの作成(Ludia)
●既存のテーブルtbl1のカラムcol1への全文検索インデックス付与
 (形態素を見出し語として採用)
 CREATE INDEX ft ON tbl1 USING fulltext(col1);

●既存のテーブルtbl1のカラムcol1への全文検索インデックス付与
 (bi-gramを見出し語として採用)
 CREATE INDEX ft ON tbl1 USING fulltextb(col1);

リスト2のクエリにて、テーブルtbl1のカラムcol1に全文検索インデックスが付与されました。以下のクエリで全文検索を行うことができます。Tritonnとは異なり、とくに指定をすることなくYahoo!やGoogleのようなAND検索やOR検索などをサポートしています。

 SELECT * FROM tbl1 WHERE col1 @@ '検索クエリ';

デフォルトでAND検索を行うためには、Tritonnと同様に検索クエリの頭に「*D+」を付与してください。

 SELECT * FROM tbl1 WHERE col1 @@ '*D+ 検索クエリ';

もちろん、他カラムを用いての絞込みやソートを行うことが可能です。

 SELECT * FROM tbl1 WHERE col1 @@ '*D+ 検索クエリ' AND id < 1000 ORDER BY id;

スコアは、関数pgs2getscoreを呼び出すことによって取得できます。関数pgs2getscoreは、第1引数にTIDと呼ばれるPostgreSQL内部でのレコードのIDを、第2引数にインデックス名を指定します。TIDは、⁠テーブル名.ctid」で取得することが可能です。

 SELECT col1, pgs2getscore(tbl1.ctid, 'ft') FROM tbl1 WHERE col1 @@ '検索クエリ';

Ludia使用上のTips

Tritonnに続いて、Ludiaを使用する上で知っておくとうれしい(!?)事項を3つ紹介いたします。

1)シーケンシャルスキャンにまつわる問題

Ludiaでは、全文検索インデックスを付与しなくても、全文検索と同様のクエリで検索を行う「シーケンシャルスキャン」機能が搭載されています。しかし、この機能は大変速度が遅いです。Ludiaでは、EXPLAINコマンドを利用することによって、全文検索を含めたすべてのインデックスがどのように利用されているかを正しく確認することができます。 検索速度が遅い場合には、EXPLAINコマンドを使って「シーケンシャルスキャン」が行われているかどうかを確認してください。全文検索インデックスを付与していても、PostgreSQLは他のインデックスとの兼ね合いで「シーケンシャルスキャン」を実行してしまう場合があります。このような場合には、

SET enable_seqscan TO off;

上記のクエリを実行してシーケンシャルスキャンを抑制するか、PostgreSQLの設定ファイルであるpostgresql.confの中に以下の1行を追加してください。

enable_seqscan = off

これらの設定は、全てのクエリの実行に影響します。よって、⁠シーケンシャルスキャン」を抑制したいクエリの直前で設定を行い、そのクエリの実行後に設定を戻すことによる一時的な設定をお勧めいたします。

2)検索結果の取得の処理

Tritonnの同様に、検索結果のすべてを取得する処理は大変時間がかかります。SELECT文においてLIMITを指定することによって、必要な検索結果のみを取得しましょう。その際に、検索結果の総件数を得るためにはpgs2getnhits()関数を利用してください。PostgreSQLでは、データベース内部の特性として、SELECT COUNT(*)のクエリが遅い傾向にあります。

3)pgs2snippet1()関数

pgs2snippet1()関数を利用することによって、Webの検索エンジンのように、コンテンツ中の検索語の前後にHTMLタグ等を付与して目立たせることができるようになります。詳細は、Ludiaの公式ページにあるドキュメントをご覧ください。

Tritonn/Ludiaに共通するTips

TritonnでもLudiaでも共通するTipsが3つあります。

1)クエリ書式の解説ページ

Tritonnの「IN BOOLEAN MODE」やLudiaで使用できる全文検索クエリの書式については、Sennaの公式サイトにあるクエリ書式の解説ページを参考にしてください。

2)検索専用のテーブル

一般的に、テーブルのJOINなどを行うと検索パフォーマンスに悪影響があります。JOINなどを行わなくて済むように、正規化を崩した検索専用のテーブルを準備することをお勧めいたします。

3)コンテンツの総サイズとメモリ

Sennaが要するメモリは、全文検索対象コンテンツの総サイズに比例します。また、付与された全文検索インデックスの数にも比例します。その上、MySQLやPostgreSQL自体が高速に動作するためにも、コンテンツの総サイズに比例したメモリが必要になります。

よって、大量のコンテンツで高速な全文検索を行いたい場合には、64bit版のOSを搭載したサーバに、メモリを多く積んでの利用をお勧めいたします。

おわりに

本稿では、MySQLとPostgreSQLにて全文検索を行うことのできるTritonnおよびLudiaについて紹介しました。これらの製品を用いた場合、Sennaを単体で使用した場合ほど、細かなチューニングなどは行えませんが、多くの開発者が親しみを持つSQLを用いて気軽に全文検索を行うことができます。

ぜひ、あなたのWebサービスにTritonn/Ludiaを使用した検索窓を付けてみてください!

おすすめ記事

記事・ニュース一覧