LudiaはPostgreSQLに高速な全文検索機能を提供します。 全文検索エンジンSennaを利用し、データベース内のテキスト情報を高速検索します。 Ludiaは以下のような特徴をもっています。
LudiaはOSS(オープンソースソフトウェア)です。 あなたは、Free Software Foundationが公表した GNU Lesser General Public Licenseのバージョン2.1が定める条項に従って、 本プログラムを再頒布または変更することができます。 頒布にあたっては、 市場性及び特定目的適合性についての暗黙の保証を含めて、 いかなる保障も行いません。 詳細は GNU LESSER GENERAL PUBLIC LICENSE Version 2.1 をお読みください。
以下の環境で動作確認をしています。
OS: | RedHat Enterprise Linux AS[ES] 4 |
---|---|
DBMS: | PostgreSQL 8.2.6 (8.3.0, 8.1.11) |
Senna: | 1.1.2 (1.1.1以前のバージョンには対応していません) |
MeCab: | 0.97 |
バグ報告や技術的な質問については、 Ludia-usersメーリングリスト でお問い合わせください。
インストール方法については、 このファイルと同じディレクトリにあるINSTALLを参照してください。
MeCab, MeCab辞書, Sennaのバージョンに変更がない場合は、 既存のインデックスをそのまま利用できます。 Ludiaを上書きでインストールした後に、 インデックスアクセスメソッドの登録 と 設定ファイルの編集 を行い、 pg_ctl restartコマンドでデータベースサーバを再起動してください。
インデックスを再構築するする必要があるのは、以下のような場合です。
この場合には、 利用中のバージョンの アンインストールスクリプトを実行し、 環境をクリーンアップしてください。 (スクリプトを実行することで、LudiaのインデックスはすべてDROPされます。)
$ psql -f /usr/local/pgsql/share/uninstall_pgsenna2.sql test
その後、通常の手順でインストールを行い、データベースサーバを再起動してください。
Ludiaを使用するデータベースに対してインデックスアクセスメソッドを登録します。 ソースアーカイブに含まれている pgsenna2.sql をpsqlから実行してください。 (pgsenna2.sqlはPostgreSQLのshareディレクトリにインストールされます。)
$ psql -f /usr/local/pgsql/share/pgsenna2.sql testdb
バージョンアップで既存の環境にインストールする場合には、 以下のようなエラーが表示されますが、無視して問題ありません。
ERROR: duplicate key violates unique constraint "pg_am_name_index" ERROR: duplicate key violates unique constraint "pg_am_name_index" ERROR: duplicate key violates unique constraint "pg_am_name_index" ERROR: operator @@ already exists ERROR: operator class "text_ops" for access method "fulltext" already exists ERROR: operator class "text_ops" for access method "fulltextb" already exists ERROR: operator class "text_ops" for access method "fulltextu" already exists
デフォルト設定でLudiaを使用する場合は、この節を飛ばしても問題ありません。
Ludiaを使用するデータベースクラスタのpostgresql.confファイルに、 以下の設定内容を追加してください。 (custom_variable_classesの項目は必須です。 それ以外の項目は記述しないと、デフォルト値が参照されます。) 設定を反映するためにはPostgreSQLを再起動する必要があります。 postgresql.confの設定が反映されていないと、 実行時にエラーになってしまうので注意してください。 設定内容についての詳細は、 実行時の設定 の節を参照してください。
custom_variable_classes = 'ludia' ludia.max_n_sort_result = 10000 ludia.enable_seqscan = on ludia.seqscan_flags = 1 ludia.sen_index_flags = 31 ludia.max_n_index_cache = 16 ludia.initial_n_segments = 512
もしすでにcustom_variable_classesが設定されている場合は、 そこにludiaというクラス名を追加してください。
ここでは、例として以下のようなテーブルを利用します。
CREATE TABLE table1 (col1 text, col2 varchar(128)); INSERT INTO table1 VALUES ('すもももももももものうち', 'あの壺はよいものだ'); INSERT INTO table1 VALUES ('ももから生まれた桃太郎', 'あの壷はよいものだ');
全文検索インデックスはCREATE INDEX 文を利用して作成します。
CREATE INDEX index1 ON table1 USING fulltext(col1);
Ludiaがインデックス対象とできるのはtext型のみなので、 char型などの列に対してインデックスを作成したい場合はキャストしてください。
CREATE INDEX index2 ON table1 USING fulltextb((col2::text));
インデックスアクセスメソッド名には
の3種類があり、どれを指定するかによってSennaインデックスのフラグが変わります。 ユーザ定義(fulltextu)についての詳細は Sennaインデックス作成時のオプション の節を参照してください。
Ludiaのインデックスを用いた検索を行う場合には @@ 演算子を使用します。 @@ 演算子の右辺には Sennaの検索クエリ を指定してください。
SELECT * FROM table1 WHERE col1 @@ 'もも'; col1 | col2 --------------------------+-------------------- すもももももももものうち | あの壺はよいものだ ももから生まれた桃太郎 | あの壷はよいものだ (2 rows)
また、この検索における検索スコアを取得するためには、 pgs2getscore関数を利用します。 pgs2getscore関数は2つの引数をとります。 1番目の引数には検索対象となった行のTIDを、 2番目の引数にはインデックス名を指定してください。
SELECT col1, pgs2getscore(table1.ctid, 'index1') FROM table1 WHERE col1 @@ 'もも'; col1 | pgs2getscore --------------------------+-------------- すもももももももものうち | 10 ももから生まれた桃太郎 | 5
PostgreSQLのインデックスリレーションファイルと、 Ludiaのインデックスファイルは以下の5つから構成されます。 (テーブル空間を使用している場合は、テーブル空間定義時に指定した場所に置かれます。)
1 はPostgreSQLのインデックスリレーションファイル、 2~5はSennaのインデックスファイルです。 2~5のファイルは手作業で削除する必要があります。
参考として、インデックスのファイルノード番号は以下のようなクエリで取得できます。
SELECT relfilenode FROM pg_class WHERE relname = 'index1';
また、データベースのOIDは以下のようなクエリで取得できます。
SELECT oid FROM pg_database WHERE datname = 'dbname';
1のファイルについては、DROP INDEXを実行することで削除されます。
DROP INDEX index1;
あるいは、pgs2destroy関数を利用すると、 データベース中の不要になったSennaインデックスファイルを一括して削除できます。 pgs2destroy関数は、2~5が存在するが1のファイルが存在しない、という場合に、 2~5のファイルを削除します。
# DROP TABLE table1; DROP TABLE # SELECT pgs2destroy(); pgs2destroy ------------- 1 (1 row)
関数の返り値は、削除したインデックス数です。 (上記の2~5のファイルで1セットです。)
PostgreSQL8.3では、DROP INDEXの直後には1のファイルが消えず、 チェックポイント処理後に1のファイルが削除されます。 そのため、DROP INDEXの直後にpgs2destroy関数を実行しても、 2~5のファイルは削除されません。 pgs2destroy関数を使用する場合は、CHECKPOINTコマンドを予め実行してください。
@@演算子を用いた全文検索条件を指定しても、シーケンシャルスキャンが実行された場合には、 インデックススキャンの場合と同様の検索を行うことができません。 具体的には、スコアの取得、高速ヒット関数、近傍検索 *N 、類似検索 *S ができません。 (空白で区切った複数検索キーによる検索や、Senna演算子+、-などのAND, OR検索は可能です。) そのためLudiaでは、シーケンシャルスキャンが実行された場合に エラーにする設定があります。 (以下の例ではenable_indexscanをoffにして、 強制的にシーケンシャルスキャンを実行しています。)
# SET enable_indexscan TO off; SET # EXPLAIN SELECT col1 FROM table1 WHERE col1 @@ 'もも'; QUERY PLAN ------------------------------------------------------- Seq Scan on table1 (cost=0.00..1.02 rows=1 width=32) Filter: (col1 @@ 'もも'::text) (2 rows) # SELECT col1 FROM table1 WHERE col1 @@ 'もも'; ERROR: pgsenna2: sequencial scan disabled.
この設定はpostgresql.confのludia.enable_seqscan変数で指定されますが、 SETコマンドでも変更することができます。 (SETコマンドによる変更はそのセッション内でのみ有効です。)
# SET ludia.enable_seqscan TO on; SET # SELECT col1 FROM table1 WHERE col1 @@ 'もも'; col1 -------------------------- すもももももももものうち ももから生まれた桃太郎 (2 rows)
インデックスを張っていないカラムに対して@@演算子指定した場合も、 Senna演算子を利用したシーケンシャルスキャンとなります。 (シーケンシャルスキャンではスコアの取得ができません。)
# SELECT col1 FROM table1 WHERE col1 @@ 'もも + 桃太郎'; col1 -------------------------- ももから生まれた桃太郎 (1 rows)
上述したように、postgresql.confのludia.enable_seqscanをonにすると、 シーケンシャルスキャンが可能となります。 その際、ludia.seqscan_flagsを1にした場合は、 シーケンシャルスキャンは比較文字列を正規化した後、スキャンを行います。 ludia.seqscan_flagsを0にした場合は、正規化せずにスキャンを実行します。
ここで言う正規化とは、 Sennaインデックス作成時のオプション の項にある、 SEN_INDEX_NORMALIZEと同様の操作を指します。
下記の例ではインデックスが張っていないカラムで検索を行っています。
# EXPLAIN SELECT * FROM tab WHERE col @@ 'test'; QUERY PLAN --------------------------------------------------------- Seq Scan on seqscan (cost=0.00..25.38 rows=1 width=32) Filter: (col @@ 'test'::text) (2 rows) # SHOW ludia.seqscan_flags; ludia.seqscan_flags --------------------- 1 (1 row) # SELECT * FROM tab WHERE col @@ 'test'; col ---------- test TesT TEST (3 rows)
postgresql.confのludia.max_n_sort_resultを設定していると、 検索でヒットした行のうち、スコア上位のものから max_n_sort_result件だけが返却されます。 ただし、クエリの最終的な結果セットは 必ずしもスコア順にソートされているわけではありません。 ソートが必要な場合にはORDER BYを利用してください。
# SHOW ludia.max_n_sort_result; ludia.max_n_sort_result ------------------------- 10000 (1 row) # SELECT col1, pgs2getscore(ctid, 'index1') FROM table1 WHERE col1 @@ 'もも'; col1 | pgs2getscore --------------------------+-------------- すもももももももものうち | 10 ももから生まれた桃太郎 | 5 (2 rows)
このmax_n_sort_resultによる上限はSETコマンドでも、 セッション内で一時的に変更することができます。
# SET ludia.max_n_sort_result TO 1; SET # SELECT col1, pgs2getscore(ctid, 'index1') FROM table1 WHERE col1 @@ 'もも'; col1 | pgs2getscore --------------------------+-------------- すもももももももものうち | 10 (1 row)
また、特殊な設定として、 ludia.max_n_sort_resultを-1に設定すると上限なしという意味になります。 ただし、現状では-1に設定すると、 pgs2getscore関数によるスコアの取得が利用できなくなるという制限があります。
アクセスメソッドとしてfulltextuを選択すると、 インデックス作成時にSennaインデックスのフラグを指定することができます。 利用できるフラグは(Senna 1.0.8では)以下のような定義と意味をもっています。 (詳しくは SennaのAPIドキュメント を参照してください。)
#define SEN_INDEX_NORMALIZE 0x0001 #define SEN_INDEX_SPLIT_ALPHA 0x0002 #define SEN_INDEX_SPLIT_DIGIT 0x0004 #define SEN_INDEX_SPLIT_SYMBOL 0x0008 #define SEN_INDEX_NGRAM 0x0010 #define SEN_INDEX_DELIMITED 0x0020
postgresql.confの設定には、10進数の値を指定してください。 例えば、 SEN_INDEX_NGRAM|SEN_INDEX_NORMALIZE|SEN_INDEX_SPLIT_ALPHA というフラグを指定する場合には、
ludia.sen_index_flags = 19
となります。
Ludiaはインデックスを1つオープンするごとにメモリを確保します。 基本的には1度オープンしたインデックスは、 バックエンドプロセスが終了するまでクローズしません。 ただし、postgresql.confの設定変数の ludia.max_n_index_cacheで設定された値より多くの インデックスを開こうとすると、 もっとも最近利用されていないインデックスをクローズします。 現在オープンされているインデックスは pgs2indexcache関数で確認することができます。
SELECT name FROM pgs2indexcache();
postgresql.confの ludia.initial_n_segments / 4 [Mbyte] が インデックスの初期サイズとなります。 ludia.initial_n_segmentsがデフォルト(512)の場合、 転置インデックスの最大サイズは8GB程度になります。 一般的には インデックス対象の総テキスト量≒転置インデックスのサイズ となる傾向があるため、 これ以上のサイズのテキストに対してインデックスを作成する場合、 initial_n_segmentsの値を増やす必要があるかもしれません。
pgs2getoption関数を用いると、現在の設定を確認することができます。
# \x Expanded display is on. # SELECT * FROM pgs2getoption(); -[ RECORD 1 ]------+---- max_n_sort_result | 10000 enable_seqscan | on seqscan_flags | 1 sen_index_flags | 31 max_n_index_cache | 16 initial_n_segments | 512