pg_bigmを動かしてみた

kasa_zipさんのツイート

今朝、いつものようにツイートをチェックしてたら、kasa_zipさんのツイートが目に留まった。

あれ、pg_bigmリリースしたんだ?

むむむ、pg_bigmとな。名前から想像するにPostgreSQL標準の全文検索モジュールpg_trgmのbi-gramバージョンってことだよな、きっと。
2月のPostgreSQL Unconferenceのときに、NTT-DATAさんがpg_trgmの使い方とその問題点を発表していたから、このモジュールはpg_trgmの問題点(1,2文字の条件を与えたときの性能問題)をbi-gramにして解決したものに違いない(飲み会のときにfujii_masaoさん全文検索系の話題のときに4月になったら云々言ってたような気がするし・・・もう4月になったから書いてもいいよね?)。
ということで、pg_bigmのページから早速DLして動かして見ることにした。

pg_bigmの動作条件

ドキュメントを読むと、RH6系+PostgreSQL 9.1での動作実績はあるらしい。9.2は未検証とのこと。
でも、まあ動くんじゃないかな〜、手元に9.1の環境を作るのもめんどいし〜ということで、とりあえず手元の9.2環境で動かして見ることにした。9.2上での動作検証結果を開発スタッフにフィードバックするのも大事だし・・・。というか単なる面倒くさがりというのが真相だが。

ちなみに手元の環境は

あ、一つ重大な環境制約があった。

  • pg_trgmとpg_bigmは同じデータベース上に共存できない。

むむむ・・・いろんな全文検索系を検証するときに、同じデータを使いたいからこの制約はちょっと面倒臭い。せっかく、pg_hint_planで同じテーブルに張った複数種類のインデクスをHINT句で制御しよう・・・つまりpg_trgm用のインデクスとpg_bigm用のインデクスを両方指定して、HINT句でインデクスを指定して処理を切り替えようと思っていたのに(´・ω・`)

ビルド&インストール

ビルド&インストールは特に問題なし。
普通に USE_PGXS=1 オプションをつけて make と make install check を行うだけ。
テキトーにデータベースを作成してCREATE EXTENSIONで pg_bigm EXTENSIONを登録する。

[ぬこ@横浜]$ psql bigm
psql (9.2.4)
Type "help" for help.

bigm=# CREATE EXTENSION 
neo4j_fdw  pg_bigm    
bigm=# CREATE EXTENSION pg_bigm ;
CREATE EXTENSION
bigm=# \dx pg_bigm
                    List of installed extensions
  Name   | Version | Schema |              Description              
---------+---------+--------+---------------------------------------
 pg_bigm | 1.0     | public | text index searching based on bigrams
(1 row)

bigm=# 

pg_bigmの概要

せっかくドキュメントもきちんと用意してあるから読もう。
基本的にはpg_trgmの問題であった

  • ヘッダを変更してリビルドしないと日本語が使えない
  • (日本語として検索対象となりがちな)1,2文字の文字列に対する検索性能の向上

を改善したもの、という感じだろうか。
元々pg_trgmで持っていた機能は現時点では完全にサポートはしてないけど(GiSTインデクスが未サポートなのと、類似検索への対応がないのがpg_trgmと比較すると気になるところ)、このあたりは今後、改善されていくんだと思う。

あと、pg_trgmに元々なかった(tsearch系にあるような)スコア/スニペット的な機能の追加はない。
なので、基本的にはpg_trgmと同じ適用領域ってことになるのかな。

とりあえず9.2上でうごかしてみた

マニュアル上は、postgresql.conf のshared_preload_librariesの設定が必須、とあるけど独自パラメータを指定しなければ(デフォルトのままであれば)なくても動くんじゃなイカ

それはともかく、インデクスを設定して検索してみる。
まず、データをロードしてインデクスを作成する。インデクスメソッドにはGINを、演算子クラスには gin_bigm_ops を指定する。

bigm=# CREATE TABLE test (id serial, data text);
NOTICE:  CREATE TABLE will create implicit sequence "test_id_seq" for serial column "test.id"
CREATE TABLE
bigm=# COPY test(data) FROM '/tmp/meros.txt';
COPY 90
bigm=# CREATE INDEX test_bigm ON test USING gin (data gin_bigm_ops);
CREATE INDEX
bigm=# 

インデクスは生成できた。次は検索。インデクスが使われていることを確認するために、enable_seqscan=offっておく。
で、とりあえずEXPLAINでプランを確認。

bigm=# EXPLAIN SELECT id, data FROM test WHERE data LIKE '%セリヌンティウス%';
                                QUERY PLAN                                
--------------------------------------------------------------------------
 Bitmap Heap Scan on test  (cost=60.08..65.20 rows=10 width=349)
   Recheck Cond: (data ~~ '%セリヌンティウス%'::text)
   ->  Bitmap Index Scan on test_bigm  (cost=0.00..60.08 rows=10 width=0)
         Index Cond: (data ~~ '%セリヌンティウス%'::text)
(4 rows)

で、検索。

bigm=# SELECT id, data FROM test WHERE data LIKE '%セリヌンティウス%' LIMIT 3;
 id |                                                                                                                                         
                                                               data                                                                           
                                                                                                                             
----+-----------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------
  3 | メロスには竹馬の友があった。セリヌンティウスである。今は此のシラクスの市で、石工をしている。その友を、これから訪ねてみるつもりなのだ。久
しく逢わなかったのだから、訪ねて行くのが楽しみである。
 24 | 「そうです。帰って来るのです。」メロスは必死で言い張った。「私は約束を守ります。私を、三日間だけ許して下さい。妹が、私の帰りを待っている
のだ。そんなに私を信じられないならば、よろしい、この市にセリヌンティウスという石工がいます。私の無二の友人だ。あれを、人質としてここに置いて行
こう。私が逃げてしまって、三日目の日暮まで、ここに帰って来なかったら、あの友人を絞め殺して下さい。たのむ、そうして下さい。」
 30 |  竹馬の友、セリヌンティウスは、深夜、王城に召された。暴君ディオニスの面前で、佳《よ》き友と佳き友は、二年ぶりで相逢うた。メロスは、友に
一切の事情を語った。セリヌンティウスは無言で首肯《うなず》き、メロスをひしと抱きしめた。友と友の間は、それでよかった。セリヌンティウスは、縄打
たれた。メロスは、すぐに出発した。初夏、満天の星である。
(3 rows)

bigm=# 

検索できた。

次は検索クエリ関数likequery()を使った例。簡単に言えば、これを使うと%とかのメタ文字を付与しなくてもキーワードの中間一致検索をしてくれる。

bigm=# SELECT id, data FROM test WHERE data LIKE likequery('セリヌンティウス') LIMIT 3;
 id |                                                                                                                                         
                                                               data                                                                           
                                                                                                                             
----+-----------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------
  3 | メロスには竹馬の友があった。セリヌンティウスである。今は此のシラクスの市で、石工をしている。その友を、これから訪ねてみるつもりなのだ。久
しく逢わなかったのだから、訪ねて行くのが楽しみである。
 24 | 「そうです。帰って来るのです。」メロスは必死で言い張った。「私は約束を守ります。私を、三日間だけ許して下さい。妹が、私の帰りを待っている
のだ。そんなに私を信じられないならば、よろしい、この市にセリヌンティウスという石工がいます。私の無二の友人だ。あれを、人質としてここに置いて行
こう。私が逃げてしまって、三日目の日暮まで、ここに帰って来なかったら、あの友人を絞め殺して下さい。たのむ、そうして下さい。」
 30 |  竹馬の友、セリヌンティウスは、深夜、王城に召された。暴君ディオニスの面前で、佳《よ》き友と佳き友は、二年ぶりで相逢うた。メロスは、友に
一切の事情を語った。セリヌンティウスは無言で首肯《うなず》き、メロスをひしと抱きしめた。友と友の間は、それでよかった。セリヌンティウスは、縄打
たれた。メロスは、すぐに出発した。初夏、満天の星である。
(3 rows)

bigm=# 

性能測定

pg_trgmとの性能比較は・・・さすがに自分のPC(しかもVM)上で比較検証するのは微妙な気がするのでパス。
あ、でもpg_trgmとpg_bigmのインデクスサイズの差異については調べてもいいかな・・・?