出現頻度の高い用語の低速全文検索


8

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

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;

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

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

1
私の頭の上から:GINインデックスはPostgres 9.4で大幅に改善されています(次の9.5ではさらに改善されます)。それは確かに現在の9.4にアップグレードするために支払うでしょう。また、GINインデックスの代わりにGiSTのパフォーマンスも調査します。クエリの原因はORDER BY "RANK" DESCです。pg_trgm代替として、GiSTインデックスと類似性/距離演算子を使用して調査します。考えてみましょう:dba.stackexchange.com/questions/56224/...を。「より良い」結果さえ生成する可能性があります(高速であるだけでなく)。
Erwin Brandstetter 2015

PostgreSQLインスタンスを実行しているOSは何ですか?
Kassandry

これらをexplain (analyze, buffers)、できればtrack_io_timingに設定して繰り返すことはできますONか?フロッピーディスクのRAIDに保存していない限り、そのテーブルのシーケンススキャンに520秒かかるはずはありません。何かが間違いなく病的です。また、の設定random_page_cost、およびその他のコストパラメータは何ですか?
jjanes

@danjo順序付けを使用しない場合でも同じ問題に直面しています。どのように修正したか教えていただけますか?
Sahil Bahl 2017

回答:


11

疑わしいユースケース

...各CONTENTエントリは、1つのランダムな単語とすべての行で同じテキスト文字列で構成されます。

すべての行で同じテキスト文字列は、運送費だけです。それを削除して、表示する必要がある場合はビューに連結します。

明らかに、あなたはそれに気づいています:

確かにそれは現実的ではありません...しかし、私はテキストを制御することができないので...

Postgresバージョンをアップグレードする

PostgreSQL 9.3.4の実行

まだPostgres 9.3を使用している場合、少なくとも最新のポイントリリース(現在は9.3.9)にアップグレードする必要があります。プロジェクトの公式勧告:

使用しているメジャーバージョンに関係なく、すべてのユーザーが最新の利用可能なマイナーリリースを実行することを常にお勧めします。

さらに良いのは、GINインデックスの大幅な改善を受けた9.4にアップグレードすることです。

主な問題1:コスト見積もり

一部のtextsearch関数のコストは、バージョン9.4までは非常に低く見積もられています。@jjanesが最近の回答で説明しているように、そのコストは次のバージョン9.5では100倍に引き上げられます。

これが議論されたそれぞれのスレッドとTom Laneによるコミットメッセージです。

コミットメッセージでわかるように、to_tsvector()これらの関数の1つです。変更をすぐに(スーパーユーザーとして)適用できます。

ALTER FUNCTION to_tsvector (regconfig, text) COST 100;

それをしなければならない多くの可能性が高いあなたの機能インデックスが使用されていること。

主な問題2:KNN

中心的な問題は、Postgresがts_rank()260k行(rows=261011)のランクを計算してから上位5を選択する必要があることです。これは、前述のように他の問題を修正した後でも、高額になります。これは本質的にK最近傍(KNN)問題であり、関連するケースの解決策があります。しかし、ランク計算自体はユーザー入力に依存するため、私はあなたのケースの一般的な解決策を考えることはできません。低いランクの一致の大部分を早期に排除して、完全な計算が少数の優れた候補に対してのみ実行されるようにしようと思います。

考えることのできる 1つの方法は、フルテキスト検索とトライグラムの類似検索組み合わせることです。これは、KNN問題の実用的な実装を提供します。このようにして、LIKE述語との「最良」の一致を候補として(LIMIT 50たとえば、サブクエリで)事前に選択し、メインクエリのランク計算に従って上位5行を選択できます。

または、同じクエリで両方の述語を適用し、次の関連する回答のように、(異なる結果を生成する)トライグラムの類似性に従って最も近い一致を選択します。

私はさらに調査を行いましたが、この問題に最初に遭遇したのはあなたではありません。pgsql-generalの関連記事:

最終的にはtsvector <-> tsqueryオペレーターを実装する作業が進行中です。

Oleg BartunovとAlexander Korotkovは(当時の><代わりに演算子として使用して)機能するプロトタイプを提示しました<->が、Postgresに統合するのは非常に複雑です。

主な問題3:重みとインデックス

そして、クエリの速度を低下させるもう1つの要因を特定しました。ドキュメントごと:

GINインデックスは標準クエリでは損失がありませんが、そのパフォーマンスは対数的に一意の単語の数に依存します。(ただし、GINインデックスにはtsvector値の単語(語彙素)のみが格納され、重みラベルは格納されません。したがって、重みを含むクエリを使用する場合は、テーブル行の再チェックが必要です。)

大胆な強調鉱山。重みが含まれるとすぐに、各行をヒープからフェッチする必要があり(安価な可視性チェックだけでなく)、長い値をデトーストする必要があるため、コストが増加します。しかし、そのための解決策があるようです:

インデックスの定義

もう一度インデックスを見ると、そもそも意味がありません。重みを1つの列に割り当てます。これは、重みが異なる他の列を連結しない限り意味がありません。

COALESCE() また、実際にはそれ以上の列を連結しない限り、意味がありません。

インデックスを簡略化します。

CREATE INDEX "File_contentIndex" ON "File" USING gin
(to_tsvector('english', "CONTENT");

そしてあなたのクエリ:

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

すべての行に一致する検索語句では依然として高価ですが、おそらくはるかに少なくなります。

アサイド

これらすべての問題を組み合わせると、2番目のクエリで520秒という非常にコストがかかることになり始めています。しかし、まだまだ問題があるかもしれません。サーバーを構成しましたか?
パフォーマンスの最適化に関する通常のアドバイスがすべて適用されます。

二重引用符の付いたCaMeLケース識別子を使用しない場合、これにより作業が楽になります。


私もこれに遭遇しています。Postgresql 9.6では、シノニムにクエリの書き換えを使用しているため、トライグラムの類似検索を使用してドキュメントの数を制限してもうまく機能するとは思いません。
フォリー2017

すごい!USING gin (to_tsvector('english', "CONTENT")
K-Gun

1

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.