全文検索クエリでのORDER BYの最適化


8

entities1500万レコードまでの大きなテーブルがあります。の「ホッケー」に一致する上位5行を見つけたいname

に全文索引がありますname。これは次のように使用されます。gin_ix_entity_full_text_search_name

クエリ:

 SELECT "entities".*,
         ts_rank(to_tsvector('english', "entities"."name"::text),
         to_tsquery('english', 'hockey'::text)) AS "rank0.48661998202865475"
    FROM "entities" 
         WHERE "entities"."place" = 'f' 
              AND (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'hockey'::text)) 
         ORDER BY "rank0.48661998202865475" DESC LIMIT 5

期間25,623ミリ秒

計画を説明する
1制限(コスト= 12666.89..12666.89行= 5幅= 3116)
2->ソート(コスト= 12666.89..12670.18行= 6571幅= 3116)
3ソートキー:(ts_rank(to_tsvector( 'english' :: regconfig、(name):: text)、 '' 'hockey' '' :: tsquery))
4->エンティティに対するビットマップヒープスキャン(コスト= 124.06..12645.06行= 6571幅= 3116)
5 Condを再チェックします:(to_tsvector( 'english' :: regconfig、(name):: text)@@ '' 'hockey' '' :: tsquery)
6フィルター:(配置しない)
7-> gin_ix_entity_full_text_search_nameのビットマップインデックススキャン(コスト= 0.00..123.74行= 6625幅= 0)
8インデックス条件:(to_tsvector( 'english' :: regconfig、(name):: text)@@ '' 'hockey' '' :: tsquery)

インデックス条件を2回検証する理由がわかりません。(クエリプランのステップ4および7)。ブール条件(not place)が原因ですか?もしそうなら、非常に高速なクエリを取得するためにそれを自分のインデックスに追加する必要がありますか?それとも、並べ替え条件が遅くなるのですか?

EXPLAIN ANALYZE 出力:

  制限(コスト= 4447.28..4447.29行= 5幅= 3116)(実際の時間= 18509.274..18509.282行= 5ループ= 1)
  ->ソート(コスト= 4447.28..4448.41行= 2248幅= 3116)(実際の時間= 18509.271..18509.273行= 5ループ= 1)
         ソートキー:(ts_rank(to_tsvector( 'english' :: regconfig、(name):: text)、 '' 'test' '' :: tsquery))
         ソート方法:上位Nヒープソートメモリ:19kB
     ->エンティティに対するビットマップヒープスキャン(コスト= 43.31..4439.82行= 2248幅= 3116)(実際の時間= 119.003..18491.408行= 2533ループ= 1)
           条件を再確認:(to_tsvector( 'english' :: regconfig、(name):: text)@@ '' 'test' '' :: tsquery)
           フィルター:(配置しない)
           -> gin_ix_entity_full_text_search_nameのビットマップインデックススキャン(コスト= 0.00..43.20行= 2266幅= 0)(実際の時間= 74.093..74.093行= 2593ループ= 1)
                 インデックス条件:(to_tsvector( 'english' :: regconfig、(name):: text)@@ '' 'test' '' :: tsquery)
 合計実行時間:18509.381ミリ秒

これが私のDBパラメータです。HerokuがAmazonサービスでホストしています。彼らは、1.7GBのRAM、1つのプロセッシングユニット、最大1TBのDBを備えていると説明しています。

名前| 現在の設定
------------------------------ + ------------------- -------------------------------------------------- ------------------------------------
 バージョン| GCC gcc-4.4.real(Ubuntu 4.4.3-4ubuntu5)4.4.3、32ビットでコンパイルされたi486-pc-linux-gnu上のPostgreSQL 9.0.7
 archive_command | テスト-f /etc/postgresql/9.0/main/wal-ed/ARCHIVING_OFF || envdir /etc/postgresql/9.0/resource29857_heroku_com/wal-ed/env wal-e wal-push%p
 archive_mode | オン
 archive_timeout | 1分
 checkpoint_completion_target | 0.7
 checkpoint_segments | 40
 client_min_messages | 通知
 cpu_index_tuple_cost | 0.001
 cpu_operator_cost | 0.0005
 cpu_tuple_cost | 0.003
 effective_cache_size | 1530000kB
 hot_standby | オン
 lc_collat​​e | en_US.UTF-8
 lc_ctype | en_US.UTF-8
 listen_addresses | *
 log_checkpoints | オン
 log_destination | Syslog
 log_line_prefix | %u [黄色]
 log_min_duration_statement | 50ms
 log_min_messages | 通知
 logging_collector | オン
 maintenance_work_mem | 64MB
 max_connections | 500
 max_prepared_transactions | 500
 max_stack_depth | 2MB
 max_standby_archive_delay | -1
 max_standby_streaming_delay | -1
 max_wal_senders | 10
 ポート| 
 random_page_cost | 2
 server_encoding | UTF8
 shared_buffers | 415MB
 ssl | オン
 syslog_ident | resource29857_heroku_com
 TimeZone | UTC
 wal_buffers | 8MB
 wal_keep_segments | 127
 wal_level | hot_standby
 work_mem | 100MB
 (39行)

編集

ORDER BY遅い部分は次のように見えます:

d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
     ts_rank(to_tsvector('english', "entities"."name"::text),
     to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities" 
     WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text)) 
     LIMIT 5;

QUERY PLAN                                                                         
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=43.31..53.07 rows=5 width=24) (actual time=76.583..103.623 rows=5 loops=1)
->  Bitmap Heap Scan on entities  (cost=43.31..4467.60 rows=2266 width=24) (actual time=76.581..103.613 rows=5 loops=1)
     Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
     ->  Bitmap Index Scan on gin_ix_entity_full_text_search_name  (cost=0.00..43.20 rows=2266 width=0) (actual time=53.592..53.592 rows=1495 loops=1)
           Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
 Total runtime: 103.680 ms

対 とORDER BY

d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
     ts_rank(to_tsvector('english', "entities"."name"::text),
     to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities" 
     WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text)) 
     ORDER BY "rank0.48661998202865475" DESC
     LIMIT 5;

QUERY PLAN                                                                           
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit  (cost=4475.12..4475.13 rows=5 width=24) (actual time=15013.735..15013.741 rows=5 loops=1)
->  Sort  (cost=4475.12..4476.26 rows=2266 width=24) (actual time=15013.732..15013.735 rows=5 loops=1)
     Sort Key: (ts_rank(to_tsvector('english'::regconfig, (name)::text), '''banana'''::tsquery))
     Sort Method:  top-N heapsort  Memory: 17kB
     ->  Bitmap Heap Scan on entities  (cost=43.31..4467.60 rows=2266 width=24) (actual time=0.872..15006.763 rows=1495 loops=1)
           Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
           ->  Bitmap Index Scan on gin_ix_entity_full_text_search_name  (cost=0.00..43.20 rows=2266 width=0) (actual time=0.549..0.549 rows=1495 loops=1)
                 Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
 Total runtime: 15013.805 ms

なぜこれが遅いのかまだ理解できません。ビットマップヒープスキャンから同じ量の行をフェッチしているように見えますが、それだけ時間がかかりますか?


work_memを増やしてもパフォーマンスが十分に向上しない場合は、EXPLAINだけでなく、EXPLAIN ANALYZEの結果を表示してください。また、このページでクエリを実行した結果を示すのにも役立ちます。wiki.postgresql.org / wiki / Server_Configuration ハードウェアの簡単な説明も役立ちます。
kgrittn

ここに別のEXPLAIN ANALYZEがあります:(上の私の投稿の編集を参照)
xlash 2012

あなたが示すPostgreSQLの設定は、最適に近い場所ではない可能性が高いことを指摘しておきます。ただし、それはトピックから外れているため、別の質問で対処する必要があります。そうすることをお勧めします。
kgrittn 2012

EXPLAIN ANALYZEの出力を理解できない場合は、役立つWikiページがあります。wiki.postgresql.org / wiki / Using_EXPLAIN多くの人が、explain.depesz.comページを参考にしています。ヘルプをざっと見て、試してみてください。
kgrittn 2012

回答:


8

私がまだ理解していないのは、これが遅い理由です。

行をソートすることは、いくらかコストがかかることは明らかです。しかし、なぜそんなに?Postgresが
なければ、ORDER BY rank0...

  • 見つかった最初の5行を選択し、5行になったらすぐに行のフェッチを停止します。

    エンティティのビットマップヒープスキャン...行= 5 ...

  • 次にts_rank()、わずか5行を計算します。
2番目のケースでは、Postgresは

  • 条件を満たすすべての(クエリプランに応じて1495)行をフェッチします。

    エンティティに対するビットマップヒープスキャン...行= 1495 ...

  • ts_rank()それらすべてについて計算します。
  • それらすべてをソートして、計算値に従って最初の5つを見つけます。
試してみてくださいORDER BY nameだけで計算するコスト見にto_tsquery('english', 'hockey'::text))余分の行に対して、どのくらい残って複数の行をフェッチしてソートするために。


キャッシングは邪魔になります...パフォーマンスはおおざっぱに悪くなります。10行1500行。あなたの説明を理解しました。それは理にかなっている。しかし、テキスト検索を実行している間...すべてを抽出せずに適切な品質のソートのために私のインデックスを構築する方法はありますか?
xlash、2012

5

ビットマップスキャンは次のように機能します。インデックスがスキャンされ、インデックス条件に一致するタプルの場所が検索されます。ビットマップは、ビットにブールロジックを使用して、他のビットマップスキャンの結果と論理的に結合する場合があります。次に、データを保持しているページにページ番号順にアクセスし、ディスクアクセスを減らし、うまくいけば、ランダム読み取りを順次読み取りに変えます。

ビットマップインデックススキャンは、メモリに収まるように「不可逆」になる必要がある場合があります。タプルレベルからページレベルに精度が低下します。(少なくともこの1つのクエリに対して)work_memを増やすと、それを回避できます。4行目のビットマップヒープスキャンまたは6行目のフィルターをスキップすることはできませんが、5行目の再チェックをスキップできる可能性があります。


1
100MBから650MBに増加し、パフォーマンスの違いはありませんでした。(work_mem)
xlash 2012

5

編集された質問の目的は、一致するすべてのリストではなく、上位の一致のいくつかを選択することであるように見えるため、かなり異なる問題なので、別の回答を投稿します。

KNN - GiST(K最近傍-一般化された検索ツリー用)インデックスを検討することをお勧めします。これは、類似性の順にインデックスから右にプルできるため、これらのヒープタプルをすべてランダムに読み取ってソートする必要はありません。それらを見つけて、Kのベストマッチを見つけます。

これまでのところ、tsearchクエリに対してKNN-GISTのサポートを実装した人はいないと思います(実行できることは確かですが、誰かが時間をかけてそれを実行するだけの問題です)。されて行われる)アプリケーションのために働くだろう。主な違いは、トリグラム検索では、tsearchのようにステミングとシノニムに辞書を使用しないことです。完全に一致する単語のみが見つかります。

あなたの例でトライグラムを試すには、おそらく次のように "name"にインデックスを付けたいでしょう:

CREATE INDEX entities_name_trgm ON entities USING gist (name gist_trgm_ops);

その後、次のように検索できます。

SELECT
    *,
    name <-> 'banana' AS sim
  FROM entities 
  WHERE name % 'banana'
  ORDER BY sim DESC
  LIMIT 5;

使用されている演算子とORDER BY「類似性」列のエイリアスの使用に注意してください。私はそれを試してみるときに、このパターンからそれほど遠くに離れることはありません。tsvectorのインデックスは、この検索には使用されません。

現在の構成に問題がない場合(これにより、VM全体がメモリオーバーコミットから絶望的なページングに簡単に投じられる可能性があります)、おそらくこのパフォーマンスに本当に満足するでしょう。それがあなたが望む行動を持っているかどうかは私にはわかりません。

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