pg_trgmインデックスを使用した類似検索のクエリ時間が遅い


9

2つのpg_trgmインデックスをテーブルに追加しました。これは、ユーザー名、またはサインアップ中にスペルが間違っているメールアドレス( "@ gmail.con"など)でユーザーを検索する必要があるため、メールアドレスまたは名前によるあいまい検索を可能にします。ANALYZEインデックスの作成後に実行されました。

ただし、これらのインデックスのいずれかでランク付けされた検索を実行すると、ほとんどの場合非常に遅くなります。つまり、タイムアウトを長くすると、クエリ 60秒で返される場合があります、15秒という非常にまれな場合もありますが、通常はクエリがタイムアウトします。

pg_trgm.similarity_threshold0.3はのデフォルト値ですが、これを上げて0.8も違いはないようです。

この特定のテーブルには2,500万行以上があり、常に照会、更新、および挿入されます(それぞれの平均時間は2ミリ秒未満です)。セットアップは、汎用SSDストレージと多かれ少なかれデフォルトのパラメーターを備えたRDS db.m4.largeインスタンスで実行されているPostgreSQL 9.6.6です。pg_trgm拡張子はバージョン1.3です。

クエリ:

  • SELECT *
    FROM users
    WHERE email % 'chris@example.com'
    ORDER BY email <-> 'chris@example.com' LIMIT 10;
    
  • SELECT *
    FROM users
    WHERE (first_name || ' ' || last_name) % 'chris orr'
    ORDER BY (first_name || ' ' || last_name) <-> 'chris orr' LIMIT 10;
    

これらのクエリはそれほど頻繁に(1日数十回)実行する必要はありませんが、現在のテーブルの状態に基づいている必要があり、理想的には約10秒以内に返されます。


スキーマ:

=> \d+ users
                                          Table "public.users"
          Column   |            Type             | Collation | Nullable | Default | Storage  
-------------------+-----------------------------+-----------+----------+---------+----------
 id                | uuid                        |           | not null |         | plain    
 email             | citext                      |           | not null |         | extended 
 email_is_verified | boolean                     |           | not null |         | plain    
 first_name        | text                        |           | not null |         | extended 
 last_name         | text                        |           | not null |         | extended 
 created_at        | timestamp without time zone |           |          | now()   | plain    
 updated_at        | timestamp without time zone |           |          | now()   | plain    
                  | boolean                     |           | not null | false   | plain    
                  | character varying(60)       |           |          |         | extended 
                  | character varying(6)        |           |          |         | extended 
                  | character varying(6)        |           |          |         | extended 
                  | boolean                     |           |          |         | plain    
Indexes:
  "users_pkey" PRIMARY KEY, btree (id)
  "users_email_key" UNIQUE, btree (email)
  "users_search_email_idx" gist (email gist_trgm_ops)
  "users_search_name_idx" gist (((first_name || ' '::text) || last_name) gist_trgm_ops)
  "users_updated_at_idx" btree (updated_at)
Triggers:
  update_users BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE update_modified_column()
Options: autovacuum_analyze_scale_factor=0.01, autovacuum_vacuum_scale_factor=0.05

(おそらく、名前クエリにも追加unaccent()する必要があることは承知していusers_search_name_idxます…)


説明:

EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE (first_name || ' ' || last_name) % 'chris orr' ORDER BY (first_name || ' ' || last_name) <-> 'chris orr' LIMIT 10;

Limit  (cost=0.42..40.28 rows=10 width=152) (actual time=58671.973..58676.193 rows=10 loops=1)
  Buffers: shared hit=66227 read=231821
  ->  Index Scan using users_search_name_idx on users  (cost=0.42..100264.13 rows=25153 width=152) (actual time=58671.970..58676.180 rows=10 loops=1)
        Index Cond: (((first_name || ' '::text) || last_name) % 'chris orr'::text)
        Order By: (((first_name || ' '::text) || last_name) <-> 'chris orr'::text"
        Buffers: shared hit=66227 read=231821
Planning time: 0.125 ms
Execution time: 58676.265 ms

電子メール検索は名前検索よりもタイムアウトする可能性が高くなりますが、それはおそらく、電子メールアドレスが非常に似ているためです(たとえば、@ gmail.comの多数のアドレス)。

EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE email % 'chris@example.com' ORDER BY email <-> 'chris@example.com' LIMIT 10;

Limit  (cost=0.42..40.43 rows=10 width=152) (actual time=58851.719..62181.128 rows=10 loops=1)
  Buffers: shared hit=83 read=428918
  ->  Index Scan using users_search_email_idx on users  (cost=0.42..100646.36 rows=25153 width=152) (actual time=58851.716..62181.113 rows=10 loops=1)
        Index Cond: ((email)::text % 'chris@example.com'::text)
        Order By: ((email)::text <-> 'chris@example.com'::text)
        Buffers: shared hit=83 read=428918
Planning time: 0.100 ms
Execution time: 62181.186 ms

クエリ時間が遅い理由は何ですか?読み込まれるバッファの数と関係がありますか?私はこの特定の種類のクエリの最適化に関する多くの情報を見つけることができませんでした、そしてクエリはいずれにせよpg_trgmドキュメントのクエリと非常に似ています。

これは、Postgresで最適化したり、より適切に実装したりできるのでしょうか、それとも、Elasticsearchのようなものをこの特定のユースケースに適合させるのでしょうか。


1
お使いのバージョンはpg_trgm1.3以上ですか?の「\ dx」で確認できpsqlます。
jjanes

<->インデックスを使用する演算子を使用してランク付けされた上位nクエリを再現できましたか?
Colin 't Hart

設定がデフォルトであると仮定すると、類似性のしきい値を使用します。あなたが小さい結果を得ることができ、ので、多分全体的なコストがダウンして行くことができるそのように...
のMichałZaborowski

@jjanesポインタをありがとう。はい、バージョンは1.3です。
Christopher Orr

1
@MichałZaborowski質問で述べたように、私はそれを試しましたが、残念ながら改善は見られませんでした。
クリストファーOrr

回答:


1

gin_trgm_opsではなくでより良いパフォーマンスを得ることができるかもしれませんgist_trgm_ops。どちらが良いかはかなり予測できません。データとクエリ用語のテキストパターンと長さの分布に敏感です。あなたはほとんどそれを試して、それがあなたにとってどのように機能するかを見る必要があります。1つは、GINメソッドはpg_trgm.similarity_thresholdGiSTメソッドとは異なり、に非常に敏感であることです。また、pg_trgmのバージョンによっても異なります。古いバージョンのPostgreSQLから始めて、それをpg_upgradeで更新した場合、最新バージョンではない可能性があります。プランナーは、どのインデックスタイプが優れているかを予測することはできません。したがって、それをテストするには、両方を作成するだけではなく、もう一方をドロップして、プランナーに強制的に1つを使用させる必要があります。

電子メール列の特定のケースでは、それらをユーザー名とドメインに分割してから、正確なドメインで同様のユーザー名をクエリすることをお勧めします。逆も同様です。次に、主要なクラウドメールプロバイダーの極端な普及により、情報をほとんど追加しないトライグラムでインデックスが汚染される可能性が低くなります。

最後に、これのユースケースは何ですか?これらのクエリを実行する必要がある理由を知ることは、より良い提案につながる可能性があります。特に、メールが配信可能で正しい人物に送信されることが確認された後、メールで類似検索を実行する必要があるのはなぜですか?おそらく、まだ検証されていない電子メールのサブセットのみに部分的なインデックスを作成できますか?


情報をありがとう。代わりにGINインデックスを試して、しきい値を試してみます。また、はい、これは未検証のアドレスの部分インデックスを作成することの素晴らしい点です。ただし、確認済みのメールアドレスであってもあいまい一致が必要になる場合があります(たとえば、@ gmail.comアドレスのドットを忘れた場合など)。ただし、言及したように、正規化されたローカルパーツとドメイン列を持つ別のテーブルがある場合がこれに該当します。
Christopher Orr
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.