2つのpg_trgmインデックスをテーブルに追加しました。これは、ユーザー名、またはサインアップ中にスペルが間違っているメールアドレス( "@ gmail.con"など)でユーザーを検索する必要があるため、メールアドレスまたは名前によるあいまい検索を可能にします。ANALYZE
インデックスの作成後に実行されました。
ただし、これらのインデックスのいずれかでランク付けされた検索を実行すると、ほとんどの場合非常に遅くなります。つまり、タイムアウトを長くすると、クエリが 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 % '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のようなものをこの特定のユースケースに適合させるのでしょうか。
<->
インデックスを使用する演算子を使用してランク付けされた上位nクエリを再現できましたか?
pg_trgm
1.3以上ですか?の「\ dx」で確認できpsql
ます。