Ludia (PostgreSQL + Senna) で全文検索
先日のデブサミで華々しく散ってきた森川です。最近 8.3 がリリースされたPostgreSQLにLudiaという全文検索モジュールを組み込んで、MySQLのTritonnと比較してみました。
インストールについては、それぞれのサイトに書いてあるので割愛します(Ludia、Tritonn)。
今回使用したテキストは青空文庫から太宰治の作品を拝借しました。以下のようなテーブルに作品名と内容を入れています。
MySQLでは、text型ではおさまりきらない作品もあったので、contentsをlongtext型としています。インデックスは両方ともMecabを使用した単語インデックスを使用しました。インデックスのサイズは変わりません(同じテキスト・ライブラリを使用しているので当然といえば当然ですね)。
Ludiaの検索についてですが、たとえば「人間」「失格」の2つの単語が入っているもの単語の多い順に調べる場合は、以下のようにします。pgs2getscoreを使用することで、検索スコアを取得することができます。
Tritonnでは以下のようします。MATCH AGAINSTをSELECTの中で使用することで検索スコアを取得します。IN BOOLEAN MODEを指定することで、Sennaの検索クエリを使用することが可能になります。
結果については、完全に同じと言いたいところだったのですが、なぜかスコアの値が若干変わってしまいます。おそらく内部で使用しているSennaのバインディング部分が異なるからだと思うのですが、他のクエリでも完全にスコアを同じ値にすることはできませんでした。
最後にSennaの検索クエリの「*S」演算子を紹介したいと思います。これを使用すると指定した文章と関連した(両方に含まれる単語の数で決まる)文書を検索することができます。たとえば、人間失格のあらすじ(Wikipedia)から関連する文章を取得するクエリは以下のようになります。
SELECT name, pgs2getscore(ludia_test.ctid, 'fulltext_contents_index')
FROM ludia_test
WHERE contents @@ '*S「自分」は人とは違う感覚を持っており... 省略 ...'
一方Tritonnで同じことをすると以下のようになります。とりあえず似たような結果になりますが、結構違いますね。
使い方に問題があるのかもしれませんが、使用してみた感じでは、完全にTritonnと同じ結果を出すのは難しいようです。ただ、普通に単語検索として使う分にはほとんど結果は変わらないので、問題は特に感じませんでした。
Sennaを使った全文検索といえば、MySQLというイメージがあったのですが、PostgreSQLでも特に問題ないということがわかりました。実際、公式サイトを見る感じでは、色々と実績はあるようなので、これから機会があれば使ってみたいと思います。
インストールについては、それぞれのサイトに書いてあるので割愛します(Ludia、Tritonn)。
今回使用したテキストは青空文庫から太宰治の作品を拝借しました。以下のようなテーブルに作品名と内容を入れています。
- PostgreSQL:
CREATE TABLE ludia_test ( id serial primary key, name text, contents text ); CREATE INDEX fulltext_contents_index ON ludia_test USING fulltext(contents); - MySQL:
CREATE TABLE ludia_test ( id integer not null auto_increment primary key, name text, contents longtext ); CREATE FULLTEXT INDEX fulltext_contents_index ON ludia_test(contents);
MySQLでは、text型ではおさまりきらない作品もあったので、contentsをlongtext型としています。インデックスは両方ともMecabを使用した単語インデックスを使用しました。インデックスのサイズは変わりません(同じテキスト・ライブラリを使用しているので当然といえば当然ですね)。
Ludiaの検索についてですが、たとえば「人間」「失格」の2つの単語が入っているもの単語の多い順に調べる場合は、以下のようにします。pgs2getscoreを使用することで、検索スコアを取得することができます。
- SELECT
name, pgs2getscore(ludia_test.ctid, 'fulltext_contents_index') - FROM
ludia_test - WHERE
contents @@ '*D+ 人間 失格'
Tritonnでは以下のようします。MATCH AGAINSTをSELECTの中で使用することで検索スコアを取得します。IN BOOLEAN MODEを指定することで、Sennaの検索クエリを使用することが可能になります。
- SELECT
name, match(contents) against('*D+ 人間 失格') as score - FROM
ludia_test - WHERE
match(contents) against('*D+ 人間 失格' IN BOOLEAN MODE) - ORDER
BY score DESC;
結果については、完全に同じと言いたいところだったのですが、なぜかスコアの値が若干変わってしまいます。おそらく内部で使用しているSennaのバインディング部分が異なるからだと思うのですが、他のクエリでも完全にスコアを同じ値にすることはできませんでした。
- Ludia
name | pgs2getscore - --------------+--------------
人間失格 | 455 鉄面皮 | 40 桜桃 | 20 懶惰の歌留多 | 20 俗天使 | 15 二十世紀旗手 | 10 - Tritonn
- +--------------------+-------+
- |
name | score | - +--------------------+-------+
- |
人間失格 | 465 | - |
鉄面皮 | 45 | - |
桜桃 | 20 | - |
懶惰の歌留多 | 20 | - |
俗天使 | 15 | - |
二十世紀旗手 | 10 | - +--------------------+-------+
最後にSennaの検索クエリの「*S」演算子を紹介したいと思います。これを使用すると指定した文章と関連した(両方に含まれる単語の数で決まる)文書を検索することができます。たとえば、人間失格のあらすじ(Wikipedia)から関連する文章を取得するクエリは以下のようになります。
SELECT name, pgs2getscore(ludia_test.ctid, 'fulltext_contents_index')
FROM ludia_test
WHERE contents @@ '*S「自分」は人とは違う感覚を持っており... 省略 ...'
name | pgs2getscore - -----------------------------------------------+--------------
人間失格 | 12475 畜犬談 | 4835 思ひ出 | 3760 お伽草紙 | 2695 もの思う葦 ――当りまえのことを当りまえに語る。 | 2345 男女同権 | 2060 女の決闘 | 1575 新釈諸国噺 | 1570 苦悩の年鑑 | 1410 善蔵を思う | 1365 パンドラの匣 | 1360 津軽 | 1130 正義と微笑 | 1060 女神 | 1060 - ...
省略 ... - (47
rows)
一方Tritonnで同じことをすると以下のようになります。とりあえず似たような結果になりますが、結構違いますね。
- +--------------------+-------+
- |
name | score | - +--------------------+-------+
- |
人間失格 | 5085 | - |
猿面冠者 | 4225 | - |
答案落第 | 4225 | - |
畜犬談 | 3180 | - |
服装に就いて | 2120 | - |
正義と微笑 | 1905 | - |
パンドラの匣 | 1410 | - |
お伽草紙 | 1060 | - |
善蔵を思う | 1060 | - |
盲人独笑 | 1060 | - ...
省略 ... - 20
rows in set (0.00 sec)
使い方に問題があるのかもしれませんが、使用してみた感じでは、完全にTritonnと同じ結果を出すのは難しいようです。ただ、普通に単語検索として使う分にはほとんど結果は変わらないので、問題は特に感じませんでした。
Sennaを使った全文検索といえば、MySQLというイメージがあったのですが、PostgreSQLでも特に問題ないということがわかりました。実際、公式サイトを見る感じでは、色々と実績はあるようなので、これから機会があれば使ってみたいと思います。
コメント
コメントフォーム
トラックバック
最近の記事
- もうすぐ健康診断があるんだ・・・ [2010年09月02日 : 阿部恵]
- Photoshopで壁紙を作りながら、基本的な使い方を覚える [2010年09月01日 : 鴨田健次]
- はじめての共同作業 Canvas編 (node.js + websocket) [2010年09月01日 : 中川善樹]
- 「PHP×Flex(後編)」PHPテクニカルセミナー(無料)第4弾の募集を開始しました!! [2010年08月26日 : 和田記光]
- 【HTML5】Canvasでお絵かきしてみた(前編) [2010年08月25日 : 橋本章史]
- MacにgroongaのMySQL用ストレージエンジン [2010年08月23日 : 笹亀弘]
- Appleのサイトで見たiPhone4をFireworksで描いてみました-1/2 [2010年08月19日 : 和田記光]
- iPad版の会社紹介を作ってみました [2010年08月19日 : 小林有佳]
- iPhoneアプリ開発開始時に気をつけるべきファイルの取り扱い (2) [2010年08月19日 : 亀本大地]
- symfonyセミナー動画無料公開! [2010年08月13日 : 岡本雄樹]



お元気ですか?
スコアが違う理由は「Ludiaがインデックススキャンを選択していないため」です。
レコード数を増やして、調査してみてください。
インデックススキャンが選択され、(たぶん)Tritonnと同じ結果になると思います。
ちなみに、インデックススキャンが選択されているかどうかは、
EXPLAIN SELECT name FROM ludia_test WHERE contents @@ '*D+ 人間 失格'
でチェックできます。
PostgreSQL8.3+Ludia1.5(来月リリース予定)では、
レコードが少なくても、インデックススキャンが選択されます。