View READMEcategory(Tag) treefile info
Ludia 1.0.0 READMELudiaについて概要LudiaはPostgreSQLに高速な全文検索機能を提供します。 全文検索エンジンSennaを利用し、データベース内のテキスト情報を高速検索します。 Ludiaは以下のような特徴をもっています。
ライセンスLudiaはOSS(オープンソースソフトウェア)です。 あなたは、Free Software Foundationが公表した GNU Lesser General Public Licenseのバージョン2.1が定める条項に従って、 本プログラムを再頒布または変更することができます。 頒布にあたっては、 市場性及び特定目的適合性についての暗黙の保証を含めて、 いかなる保障も行いません。 詳細は GNU LESSER GENERAL PUBLIC LICENSE Version 2.1 をお読みください。 制限事項
動作環境以下の環境で動作確認をしています。
連絡先バグ報告や技術的な質問については、 Ludia-usersメーリングリスト でお問い合わせください。 インストールインストール方法については、 このファイルと同じディレクトリにあるINSTALLを参照してください。 使い方インデックスアクセスメソッドの登録Ludiaを使用するデータベースに対してインデックスアクセスメソッドを登録します。 ソースアーカイブに含まれている pgsenna2.sql をpsqlから実行してください。 (pgsenna2.sqlはPostgreSQLのshareディレクトリにインストールされます。): $ psql -f /usr/local/pgsql/share/pgsenna2.sql testdb 設定ファイルの編集Ludiaを使用するデータベースクラスタのpostgresql.confファイルに、 以下の設定内容を追加してください。 設定を反映するためにはPostgreSQLを再起動する必要があります。 postgresql.confの設定が反映されていないと、 実行時にエラーになってしまうので注意してください。 設定内容についての詳細は、 実行時の設定 の節を参照してください。: custom_variable_classes = 'ludia' ludia.max_n_sort_result = 10000 ludia.enable_seqscan = on ludia.sen_index_flags = 31 ludia.max_n_index_cache = 16 もしすでに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セットです。) 実行時の設定シーケンシャルスキャンの抑制@@演算子を用いた全文検索条件を指定しても、シーケンシャルスキャンが実行された場合には、 インデックススキャンの場合と同様の検索を行うことができません。 具体的には、スコアの取得、高速ヒット関数、近傍検索 *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. 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) 検索ヒット数の上限の設定Ludiaのデフォルトの設定では、 検索でヒットした行をスコアが高い順に postgresql.confのludia.max_n_sort_resultで設定された行数まで返却します。: # 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) Sennaインデックス作成時のオプションアクセスメソッドとしてfulltextuを選択すると、 インデックス作成時にSennaインデックスのフラグを指定することができます。 利用できるフラグは(Senna 1.0.1では)以下のような定義と意味をもっています。 (詳しくは 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 となります。 使い方(応用編)ヒット件数を高速に取得するpgs2getnhits関数を用いると、 セッション内で最後に行われたSennaの検索ヒット件数を取得することができます。: # SELECT * FROM table1 WHERE col1 @@ 'もも'; col1 | col2 --------------------------+-------------------- すもももももももものうち | あの壺はよいものだ ももから生まれた桃太郎 | あの壷はよいものだ (2 rows) # SELECT pgs2getnhits(); pgs2getnhits -------------- 2 (1 row) これを利用すると、ヒット件数が非常に多い場合でも、 LIMIT句と組み合わせて利用することで、高速にヒット件数を取得することができます。: # SELECT * FROM table1 WHERE col1 @@ 'もも' LIMIT 0; col1 | col2 ------+------ (0 rows) # SELECT pgs2getnhits(); pgs2getnhits -------------- 2 (1 row) ただし、ここで得られるヒット件数はSennaの検索結果についての値であるため、 以下に挙げるような制限があります。
テキストフィルタを利用するLudiaのユーティリティ関数を利用することで、PDFファイルに対してインデックスを作成することができます。 ここでは Xpdf というツールに含まれている、pdftotextというコマンドを利用します。 まずはXpdfと日本語サポートパッケージをインストールしてください。 Ludiaではpdftotextを利用するための関数が2種類用意されています、 pgs2pdftotext1関数は、PDFファイルのpathを引数としてとり、 pdftotextを呼び出してPDFファイルからテキストを取り出します。: # select pgs2pdftotext1('/tmp/PostgresForest.pdf'); pgs2pdftotext1 ----------------- 高性能・高信頼の並列分散データベース環境を低コストで実現 複数ノード上 でそれぞれ稼動している PostgreSQL をシングルシステムイメー ジとしてユー ザに提供 PostgreSQL と互換性があるため、アプリケーション開発時に新たな トレーニン グが不要 オープンソースでのシステム構築可能性を向上 ...(省略) また、pgs2pdftotext2関数はPDFファイルそのものをbytea型のデータとして受け取り、 (それをtmpディレクトリに一時ファイルとして書き出して) pdftotextを呼び出し、PDFファイルからテキストを書き出します。: # select pgs2pdftotext1('\\120\\104\\106\\055\\061\\056\\064\\012...(省略)'); ここでは例として、以下のようなテーブルを使用します。: # CREATE TABLE pdffiles (id SERIAL PRIMARY KEY, filepath text, filedata bytea); # \d pdffiles Table "public.pdffiles" Column | Type | Modifiers ----------+---------+------------------------------------------------------- id | integer | not null default nextval('pdffiles_id_seq'::regclass) filepath | text | filedata | bytea | Indexes: "pdffiles_pkey" PRIMARY KEY, btree (id) PDFファイルは、
のいずれかの方法で格納されているとします。: # SELECT id, filepath, substring(encode(filedata, 'hex') from 1 for 30) FROM pdffiles; id | filepath | substring ----+-------------------------+-------------------------------- 1 | /tmp/PostgresForest.pdf | 255044462d312e340a25c7ec8fa20a (1 row) 1の場合にはpgs2pdftotext1関数を、2の場合にはpgs2pdftotext2関数を利用して 関数インデックスを作成することができます。: # CREATE INDEX pidx1 on pdffiles USING fulltextb(pgs2pdftotext1(filepath)); CREATE INDEX # CREATE INDEX pidx2 on pdffiles USING fulltextb(pgs2pdftotext2(filedata)); CREATE INDEX # \d pdffiles Table "public.pdffiles" Column | Type | Modifiers ----------+---------+------------------------------------------------------- id | integer | not null default nextval('pdffiles_id_seq'::regclass) filepath | text | filedata | bytea | Indexes: "pdffiles_pkey" PRIMARY KEY, btree (id) "pidx1" fulltextb (pgs2pdftotext1(filepath)) "pidx2" fulltextb (pgs2pdftotext2(filedata)) このインデックスを利用することで、PDFファイル中のテキストに対する検索を行うことができます。 検索を実行する際にも列名に対して関数を適用してください。 (検索の際には関数は実行されません。): # SELECT id FROM pdffiles WHERE pgs2pdftotext1(filepath) @@ '高性能'; id ---- 1 (1 row) # SELECT id FROM pdffiles WHERE pgs2pdftotext2(filedata) @@ '高信頼'; id ---- 1 (1 row) ここで、PDFファイルに複製不可やパスワードの設定が行われていると、 この関数はエラーを返すことに注意してください。 Snippetを作成するpgs2snippet1関数を用いると、Snippet (KWIC)を作成することができます。: # SELECT pgs2snippet1(1, 32, 1, '<em>', '</em>', 0, '筋肉痛', '怪我もなく東京マラソンを完走したが、翌日は筋肉痛のため有給休暇を取得した。'); pgs2snippet1 ------------------------------- 、翌日は<em>筋肉痛</em>のため (1 row) 引数の詳細は以下の通りとなります。
また、以下のように用いることで、検索結果のSnippetを作成することもできます。: # SELECT pgs2snippet1(1, 32, 1, '<em>', '</em>', -1, '筋肉痛', col1) FROM table1 WHERE col1 @@ '筋肉痛'; pgs2snippet1 ---------------------------------------------------------------- 、翌日は<em>筋肉痛</em>のため (1 row) インデックス情報を取得するpsg2indexinfo関数を用いると、Ludiaのインデックスの情報を取得することができます。: # \x Expanded display is on. # SELECT * FROM pgs2indexinfo(); -[ RECORD 1 ]------+------ filename | 49650 dead_flag | 0 key_size | 6 flags | 17 initial_n_segments | 512 encoding | 3 nrecords_keys | 110 file_size_keys | 8462336 nrecords_lexicon | 2432 file_size_lexicon | 8462336 inv_seg_size | 125997056 inv_chunk_size | 13516 それぞれのカラムの意味は以下の通りとなります。
バージョンを表示するpgs2version関数を用いると、Ludiaのバージョンを見ることができます。: # SELECT pgs2version(); pgs2version ------------- ludia 1.0.0 (1 row) |