web-dev-qa-db-ja.com

出現頻度の高い用語の全文検索が遅い

テキスト文書から抽出されたデータを含むテーブルがあります。データは、GINを使用してこのインデックスを作成した"CONTENT"という列に格納されます。

CREATE INDEX "File_contentIndex"
  ON "File"
  USING gin
  (setweight(to_tsvector('english'::regconfig
           , COALESCE("CONTENT", ''::character varying)::text), 'C'::"char"));

次のクエリを使用して、テーブルで全文検索を実行します。

SELECT "ITEMID",
  ts_rank(setweight(to_tsvector('english', coalesce("CONTENT",'')), 'C') , 
  plainto_tsquery('english', 'searchTerm')) AS "RANK"
FROM "File"
WHERE setweight(to_tsvector('english', coalesce("CONTENT",'')), 'C') 
  @@ plainto_tsquery('english', 'searchTerm')
ORDER BY "RANK" DESC
LIMIT 5;

Fileテーブルには250 000行が含まれ、各"CONTENT"エントリは1つのランダムなWordとすべての行で同じテキスト文字列で構成されます。

ここで、ランダムな単語(テーブル全体で1ヒット)を検索すると、クエリは非常に高速に実行されます(<100ミリ秒)。ただし、すべての行にあるWordを検索すると、クエリの実行が非常に遅くなります(10分以上)。

EXPLAIN ANALYZEは、1ヒット検索でビットマップインデックススキャンに続いてビットマップヒープスキャンが実行されることを示しています。遅い検索では、代わりにSeq Scanが実行されますが、これは非常に時間がかかっています。

もちろん、すべての行に同じデータを含めることは現実的ではありません。しかし、ユーザーがアップロードしたテキストドキュメントやユーザーが実行する検索を制御できないため、同様のシナリオが発生する可能性があります(DBで非常に出現頻度の高い用語で検索)。このようなシナリオで検索クエリのパフォーマンスを向上させるにはどうすればよいですか?

PostgreSQL 9.3.4の実行

EXPLAIN ANALYZEからクエリプラン:

クイック検索(DBで1ヒット)

"Limit  (cost=2802.89..2802.90 rows=5 width=26) (actual time=0.037..0.037 rows=1 loops=1)"
"  ->  Sort  (cost=2802.89..2806.15 rows=1305 width=26) (actual time=0.037..0.037 rows=1 loops=1)"
"        Sort Key: (ts_rank(setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char"), '''wfecg'''::tsquery))"
"        Sort Method: quicksort  Memory: 25kB"
"        ->  Bitmap Heap Scan on "File"  (cost=38.12..2781.21 rows=1305 width=26) (actual time=0.030..0.031 rows=1 loops=1)"
"              Recheck Cond: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''wfecg'''::tsquery)"
"              ->  Bitmap Index Scan on "File_contentIndex"  (cost=0.00..37.79 rows=1305 width=0) (actual time=0.012..0.012 rows=1 loops=1)"
"                    Index Cond: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''wfecg'''::tsquery)"
"Total runtime: 0.069 ms"

検索が遅い(DBで25万ヒット)

"Limit  (cost=14876.82..14876.84 rows=5 width=26) (actual time=519667.404..519667.405 rows=5 loops=1)"
"  ->  Sort  (cost=14876.82..15529.37 rows=261017 width=26) (actual time=519667.402..519667.402 rows=5 loops=1)"
"        Sort Key: (ts_rank(setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char"), '''cyberspace'''::tsquery))"
"        Sort Method: top-N heapsort  Memory: 25kB"
"        ->  Seq Scan on "File"  (cost=0.00..10541.43 rows=261017 width=26) (actual time=2.097..519465.953 rows=261011 loops=1)"
"              Filter: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''cyberspace'''::tsquery)"
"              Rows Removed by Filter: 6"
"Total runtime: 519667.429 ms"
8
danjo

同様の問題がありました。私は、field:table Tupleに対して人気のあるすべてのテキストクエリ用語のts_rankを事前計算し、それをルックアップテーブルに格納することで、問題を解決しました。これにより、テキストの重いコーパスで人気のある単語を検索する際に多くの時間(40倍)を節約できました。

  1. ドキュメントをスキャンしてその出現回数をカウントすることにより、コーパス内の人気のある単語を取得します。
  2. 最も人気のある単語で並べ替えます。
  3. 人気のある単語のts_rankを事前に計算し、テーブルに格納します。

クエリ:このテーブルを検索し、ソートされたドキュメントIDをそれぞれのランクで取得します。ない場合は、古い方法で行います。

1
Pari Rajaram