Wir haben einer Tabelle zwei pg_trgm-Indizes hinzugefügt, um die Fuzzy-Suche nach E-Mail-Adresse oder Name zu ermöglichen, da wir Benutzer nach Namen oder E-Mail-Adressen suchen müssen, die bei der Anmeldung falsch geschrieben wurden (z. B. "@ gmail.con"). ANALYZE
wurde nach der Indexerstellung ausgeführt.
In den allermeisten Fällen ist die Suche nach einem dieser Indizes jedoch nur sehr langsam. Bei einem erhöhten Zeitlimit wird eine Abfrage möglicherweise innerhalb von 60 Sekunden zurückgegeben, in sehr seltenen Fällen bis zu 15 Sekunden. In der Regel tritt jedoch eine Zeitüberschreitung bei Abfragen auf.
pg_trgm.similarity_threshold
ist der Standardwert von 0.3
, aber dies zu 0.8
erhöhen schien keinen Unterschied zu machen.
Diese bestimmte Tabelle hat über 25 Millionen Zeilen und wird ständig abgefragt, aktualisiert und eingefügt (die durchschnittliche Zeit für jede Tabelle liegt unter 2 ms). Das Setup ist PostgreSQL 9.6.6, das auf einer RDS-Instanz db.m4.large mit allgemeinem SSD-Speicher und mehr oder weniger Standardparametern ausgeführt wird. Die Erweiterung pg_trgm ist Version 1.3.
Fragen:
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;
Diese Abfragen müssen nicht sehr oft ausgeführt werden (dutzende Male am Tag), sollten jedoch auf dem aktuellen Tabellenstatus basieren und idealerweise innerhalb von etwa 10 Sekunden zurückgegeben werden.
Schema:
=> \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
(Ich bin mir bewusst , dass wir wahrscheinlich auch hinzufügen unaccent()
zu users_search_name_idx
und die Namensabfrage ...)
Erklärt:
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
Bei der E-Mail-Suche tritt mit größerer Wahrscheinlichkeit eine Zeitüberschreitung auf als bei der Namenssuche. Dies liegt jedoch vermutlich daran, dass die E-Mail-Adressen so ähnlich sind (z. B. viele @ gmail.com-Adressen).
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
Was könnte ein Grund für die langsamen Abfragezeiten sein? Hat das etwas mit der Anzahl der gelesenen Puffer zu tun? Ich konnte nicht viele Informationen zur Optimierung dieser bestimmten Art von Abfrage finden, und die Abfragen sind denen in der Dokumentation zu pg_trgm ohnehin sehr ähnlich.
Ist dies etwas, das wir in Postgres optimieren oder besser implementieren könnten, oder würde ein Blick auf Elasticsearch besser zu diesem speziellen Anwendungsfall passen?
<->
Operator bewertet wurde, der einen Index verwendet?
pg_trgm
mindestens 1.3? Sie können mit "\ dx" eincheckenpsql
.