------------------
Ludia 1.3.0 README
------------------

.. contents:: 目次


Ludiaについて
=============

概要
----

LudiaはPostgreSQLに高速な全文検索機能を提供します。
全文検索エンジンSennaを利用し、データベース内のテキスト情報を高速検索します。
Ludiaは以下のような特徴をもっています。

PostgreSQLインデックス機能への統合
    PostgreSQLのインデックスアクセスメソッドとして実装されているため、
    B-treeインデックスなど他の種類のインデックスと同じように、
    あるいは他の種類のインデックスと組み合わせて使うことができます。
    検索は追加定義の「@@」演算子を用いて行います。
    また、テーブルにレコードの追加、更新、削除を行った際は、
    インデックス側の情報も自動的に更新されます。

スコアを利用したクエリ文
    全文検索エンジンの検索スコア(検索内容との合致度)をクエリ中で取得し、
    フィルタ条件やソート条件として使用することができます。


ライセンス
----------

LudiaはOSS（オープンソースソフトウェア）です。
あなたは、Free Software Foundationが公表した
GNU Lesser General Public Licenseのバージョン2.1が定める条項に従って、
本プログラムを再頒布または変更することができます。
頒布にあたっては、
市場性及び特定目的適合性についての暗黙の保証を含めて、
いかなる保障も行いません。
詳細は GNU LESSER GENERAL PUBLIC LICENSE Version 2.1 をお読みください。


制限事項
--------

- 一意性インデックスの機能は提供しません。

- WALに対応していません。
  クラッシュリカバリやPITRを実行した場合、
  REINDEXが必要になります。

- DROP INDEXを実行すると、Sennaのインデックスファイルが残ります。
  ( インデックスの削除_ の節に削除方法があります。)

- (Ludiaのインデックスによる)CLUSTERには対応していません。

- シーケンシャルスキャンを行う場合は一部制限があります。
  シーケンシャルスキャンの抑制_ の節に詳細な説明があるので参照してください。

- テキストフィルタとしてTextPorterを使用する場合、
  別途購入する必要があります。
  ( TextPorter Ver.4 Copyright(c) 1999-2007 Antenna House, Inc. )


動作環境
--------

以下の環境で動作確認をしています。

:OS: RedHat Enterprise Linux AS[ES] 4
:DBMS:  PostgreSQL 8.2.4 (8.1.9)
:Senna: 1.0.8 (1.0.7以前のバージョンには対応していません)
:MeCab: 0.96


連絡先
------

バグ報告や技術的な質問については、
Ludia-usersメーリングリスト_ でお問い合わせください。

.. _Ludia-usersメーリングリスト:  http://lists.sourceforge.jp/mailman/listinfo/ludia-users



インストール
============

インストール方法については、
このファイルと同じディレクトリにあるINSTALLを参照してください。



バージョンアップ
================

MeCab, MeCab辞書, Sennaのバージョンに変更がない場合は、
既存のインデックスをそのまま利用できます。
Ludiaを上書きでインストールした後に、
インデックスアクセスメソッドの登録_ と 設定ファイルの編集_ を行い、
pg_ctl restartコマンドでデータベースサーバを再起動してください。

インデックスを再構築するする必要があるのは、以下のような場合です。

- MeCab, MeCab辞書のバージョンアップで分かち書きが変化した場合
- Sennaのバージョンアップでインデックスの互換性がない変更があった場合

この場合には、 **利用中のバージョンの** アンインストールスクリプトを実行し、
環境をクリーンアップしてください。
(スクリプトを実行することで、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を使用するデータベースクラスタの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));

インデックスアクセスメソッド名には

- fulltext : 正規化 + 形態素解析 (SEN_INDEX_NORMALIZE)
- fulltextb : 正規化 + 2-gram (SEN_INDEX_NORMALIZE|SEN_INDEX_NGRAM)
- fulltextu : ユーザ定義

の3種類があり、どれを指定するかによってSennaインデックスのフラグが変わります。
ユーザ定義(fulltextu)についての詳細は
Sennaインデックス作成時のオプション_ の節を参照してください。


検索の実行
----------

Ludiaのインデックスを用いた検索を行う場合には @@ 演算子を使用します。
@@ 演算子の右辺には Sennaの検索クエリ_ を指定してください。

.. _Sennaの検索クエリ : http://qwik.jp/senna/query.html

::

  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.  PGDATA/base/データベースのOID/インデックスのファイルノード番号
2.  PGDATA/base/データベースのOID/インデックスのファイルノード番号.SEN
3.  PGDATA/base/データベースのOID/インデックスのファイルノード番号.SEN.i
4.  PGDATA/base/データベースのOID/インデックスのファイルノード番号.SEN.i.c
5.  PGDATA/base/データベースのOID/インデックスのファイルノード番号.SEN.l

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セットです。)



実行時の設定
============

シーケンシャルスキャンの抑制
----------------------------

@@演算子を用いた全文検索条件を指定しても、シーケンシャルスキャンが実行された場合には、
インデックススキャンの場合と同様の検索を行うことができません。
具体的には、スコアの取得、高速ヒット関数、近傍検索 `*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
   ＴＥＳＴ
  (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)

この上限はSETコマンドでも変更することができます。
(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関数によるスコアの取得が利用できなくなります。)


Sennaインデックス作成時のオプション
-----------------------------------

アクセスメソッドとしてfulltextuを選択すると、
インデックス作成時にSennaインデックスのフラグを指定することができます。
利用できるフラグは(Senna 1.0.8では)以下のような定義と意味をもっています。
(詳しくは SennaのAPIドキュメント_ を参照してください。)

.. _SennaのAPIドキュメント : http://qwik.jp/senna/APIJ.html

::

  #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

SEN_INDEX_NORMALIZE
  英文字、数字、カタカナ、記号などは全角文字/半角文字の正規化を行い、
  英文字に関しては大文字/小文字を正規化した後、インデックスに登録する。

SEN_INDEX_SPLIT_ALPHA
  N-gramインデックスで正規化を指定した際、英文字列もN文字の要素に分割する。
  (それ以外の場合は連続した英文字列を１単語とする。)

SEN_INDEX_SPLIT_DIGIT
  N-gramインデックスで正規化を指定した際、数字文字列もN文字の要素に分割する。
  (それ以外の場合は連続した数字文字列を１単語とする。)

SEN_INDEX_SPLIT_SYMBOL
  N-gramインデックスで正規化を指定した際、記号文字列もN文字の要素に分割する。
  (それ以外の場合は、連続した記号文字列を１単語とする。)

SEN_INDEX_NGRAM
  (形態素解析ではなく)N-gramを用いる。

SEN_INDEX_DELIMITED
  (形態素解析ではなく)空白区切りで単語を区切る。

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)で十分であり、
変更する必要はありません。


設定内容の表示
--------------

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
