アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

Ludia (PostgreSQL + Senna) で全文検索

先日のデブサミで華々しく散ってきた森川です。最近 8.3 がリリースされたPostgreSQLにLudiaという全文検索モジュールを組み込んで、MySQLTritonnと比較してみました。

インストールについては、それぞれのサイトに書いてあるので割愛します(LudiaTritonn)。

今回使用したテキストは青空文庫から太宰治の作品を拝借しました。以下のようなテーブルに作品名と内容を入れています。



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でも特に問題ないということがわかりました。実際、公式サイトを見る感じでは、色々と実績はあるようなので、これから機会があれば使ってみたいと思います。