TOP業務システム> 検索ヒット件数を高速に取得
Ludia
高性能なオープンソース全文検索システム「Ludia」

第6回:柔軟に対応できるLudiaの検索機能

著者:NTTデータ  岩崎 正剛   2007/2/28
前のページ  1  2  3  次のページ
検索ヒット件数を高速に取得

   pgs2getnhitsという関数を利用することで、直前に実行された全文検索のヒット件数を取得することができます。
# SELECT pgs2getnhits();
 pgs2getnhits
--------------
         3333
(1 row)

   この関数で取得できるのは、最後に実行された全文検索の結果レコードに含まれるレコード数です。全文検索以外の条件や、ludia.max_n_sort_resultの設定はこの結果に影響しません。

   全文検索のヒット件数だけが必要な場合には、次のようにLIMIT句と組み合わせて全文検索インデックス内の検索だけを行うことで、高速に取得することができます。

# SELECT ctid FROM zip WHERE address @@ '京都' LIMIT 0;
 ctid
------
(0 rows)

# SELECT pgs2getnhits();
 pgs2getnhits
--------------
         6763
(1 row)

   ここでは「LIMIT 0」としていますが、プリペアドクエリにすると問い合わせが実行されなくなるので、その場合には「LIMIT 1」として対処してください。

   しかし、この方法には注意が必要です。インデックス作成後にテーブルを更新していくと、この方法で取得できるヒット件数と実際の問い合わせで取得できる件数の間に誤差がでてきます。これもまたPostgreSQLの構造に起因しています。この部分はPostgreSQLのデータベースを運用する上で重要なので、以降では背景を含めて少し細かく説明します。


PostgreSQLの構造

   PostgreSQLは追記型の構造を採用しているということが、その特徴として有名です。これはデータが更新・削除される場合に古いデータを残し、新しいデータを(空き領域の)末尾に追加するという手法です。この構造自体はトランザクションの仕組みのためのものですが、行を更新した場合でも行が追加されたのと同じことになるという点で、(全文検索)インデックスを作る際にも影響がでてきます。

   前回の記事では、全文検索インデックスは「単語 → ctid」の対応表だと説明しました。ctidは行の物理的な位置をあらわす値です。追記型の構造が採用されていることによって、既存の行がUPDATEされればctidの値も変わることになります。

   このことを簡単に実験して確認してみます。まず、次のようにしてテーブルを作成し、行のctidの値を確認します(もし前回の記事で作成したテスト用テーブルが残っていたら、まずそれを削除してください)。

# CREATE TABLE test (txt text);
CREATE TABLE
# CREATE INDEX idx ON test USING fulltext (txt);
CREATE INDEX
# INSERT INTO test VALUES ('hello, world');
INSERT 0 1
# INSERT INTO test VALUES ('goodby, world');
INSERT 0 1
# SELECT txt, ctid FROM test;
txt | ctid
------------------------------- + ----------------
hello, world | (0,1)
goodby, world | (0,2)
(2 rows)

   ctidが「0,1」「0,2」という値になっていることがわかります。ここでまず全文検索を実行してみると、この2行が結果として得られ、pgs2getnhits関数で得られる値も2です。

# SELECT * FROM test WHERE txt @@ 'world';
      txt
---------------
 hello, world
 goodby, world
(2 rows)

# SELECT pgs2getnhits();
 pgs2getnhits
--------------
            2

   次に、この1行目の値を更新すると、ctidの値が「0,1」から「0,3」にかわっていることがわかります。

# UPDATE test SET txt = 'new world' WHERE txt = 'hello, world';
UPDATE 1
# SELECT txt, ctid FROM test;
txt | ctid
------------------------------------------------ + ---------------
goodby, world | (0,2)
new world | (0,3)
(2 rows)

   もう一度同じ検索を行ってみると、得られる結果は2行のままなのに、pgs2getnhits関数で得られる値が3に変化しています。

# SELECT * FROM test WHERE txt @@ 'world';
      txt
---------------
 new world
 goodby, world
(2 rows)

# SELECT pgs2getnhits();
 pgs2getnhits
--------------
            3
(1 row)

   これは、UPDATEが実行されたときに、インデックス側ではINSERTが実行されたときとまったく同じ、行の追加の処理しか行われないからです。

   上記の検索の場合、全文検索インデックスからは「0,1」「0,2」「0,3」という3つの行のctidが返され、PostgreSQL側で(0,1)はすでに更新によって無効になっているということをチェックして取り除くため、正しい結果が得られます。この「ctidが無効かどうかチェックする」という処理が必要なことが、PostgreSQLがインデックス内を検索するだけでは結果数を求めることができず、countの処理に時間がかかってしまうという原因にもなっています。

   pgs2getnhits関数で取得される値はこのチェックが行われる前のものなので、無効になっている行のctidが数に入ってしまっているということです。実は無効になっている行のctidは、はじめに説明した検索ヒット数の上限の設定でも数に入ってきてしまいます。結局これらの誤差や無駄をなくすためには、ある程度更新が行われたらREINDEXを行うという運用が重要になるということです(B-Treeインデックスなどでは、VACUUMを実行することによってインデックス内の不要な行が削除されますが、Ludia 0.9.0では対応していません。これは今後のバージョンで対応予定です)。

前のページ  1  2  3  次のページ


株式会社NTTデータ 岩崎 正剛
著者プロフィール
株式会社NTTデータ  岩崎 正剛
基盤システム事業本部 オープンソース開発センタ
2002年よりNTTデータにてPCグリッドの研究開発などに従事。2006年よりOSS分野に参画し、現在はLudiaの開発、技術支援を行っている。


INDEX
第6回:柔軟に対応できるLudiaの検索機能
  Ludiaの高度な検索機能
検索ヒット件数を高速に取得
  PDFファイルの全文検索