2つのpg_trgmインデックスをテーブルに追加しました。これは、ユーザー名、またはサインアップ中にスペルが間違っているメールアドレス(例: "@ gmail.con")でユーザーを検索する必要があるため、メールアドレスまたは名前によるあいまい検索を可能にします。 ANALYZE
はインデックス作成後に実行されました。
ただし、これらのインデックスのいずれかでランク付けされた検索を実行すると、ほとんどの場合非常に遅くなります。つまり、タイムアウトが増加すると、クエリmightは60秒で戻りますが、15秒という非常にまれなケースですが、通常はクエリがタイムアウトします。
_pg_trgm.similarity_threshold
_は_0.3
_のデフォルト値ですが、これを_0.8
_に上げても違いはないようです。
この特定のテーブルには2,500万行以上があり、常に照会、更新、および挿入されます(それぞれの平均時間は2ミリ秒未満です)。セットアップは、汎用SSDストレージと多かれ少なかれデフォルトのパラメーターを備えたRDS db.m4.largeインスタンスで実行されるPostgreSQL 9.6.6です。 pg_trgm拡張はバージョン1.3です。
_SELECT *
FROM users
WHERE email % '[email protected]'
ORDER BY email <-> '[email protected]' 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
_
(_users_search_name_idx
_にunaccent()
と名前クエリも追加する必要があることは承知しています...)
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アドレスのlot)。
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE email % '[email protected]' ORDER BY email <-> '[email protected]' 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 % '[email protected]'::text)
Order By: ((email)::text <-> '[email protected]'::text)
Buffers: shared hit=83 read=428918
Planning time: 0.100 ms
Execution time: 62181.186 ms
_
クエリ時間が遅い理由は何ですか?読み込まれるバッファの数と関係がありますか?私はこの特定の種類のクエリの最適化に関する多くの情報を見つけることができませんでした、そしてクエリはとにかくpg_trgmドキュメントのクエリと非常に似ています。
これは、Postgresで最適化したり、より適切に実装したりできますか、それとも、Elasticsearchのようなものをこの特定のユースケースに適していますか?
gin_trgm_ops
ではなくGist_trgm_ops
を使用すると、パフォーマンスが向上する可能性があります。どちらが良いかはかなり予測できません。データとクエリ用語のテキストパターンと長さの分布に敏感です。あなたはほとんどそれを試して、それがあなたのためにどのように機能するかを見る必要があります。 GINメソッドは、Gistメソッドとは異なり、pg_trgm.similarity_threshold
に対して非常に敏感です。また、pg_trgmのバージョンによっても異なります。古いバージョンのPostgreSQLから始めてpg_upgrade
で更新した場合、最新バージョンではない可能性があります。プランナーは、どのインデックスタイプが優れているかを予測することはできません。したがって、それをテストするには、両方を作成するだけではなく、もう一方をドロップして、プランナーに強制的に必要なものを使用させる必要があります。
電子メール列の特定のケースでは、それらをユーザー名とドメインに分割し、正確なドメインを使用して同様のユーザー名をクエリすることをお勧めします。逆も同様です。次に、主要なクラウドメールプロバイダーの極端な普及により、情報をほとんど追加しないトライグラムでインデックスが汚染される可能性が低くなります。
最後に、これのユースケースは何ですか?これらのクエリを実行する必要がある理由を知ることは、より良い提案につながる可能性があります。特に、メールが配信可能で正しい人物に送信されることが確認された後、メールで類似検索を実行する必要があるのはなぜですか?おそらく、まだ検証されていない電子メールのサブセットのみに部分的なインデックスを作成できますか?