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 % '[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;
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 % '[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
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?
quelle
pg_trgm
mindestens 1.3? Sie können mit "\ dx" eincheckenpsql
.<->
Operator bewertet wurde, der einen Index verwendet?Antworten:
Möglicherweise können Sie eine bessere Leistung erzielen
gin_trgm_ops
als mitgist_trgm_ops
. Was besser ist, ist ziemlich unvorhersehbar, es reagiert empfindlich auf die Verteilung von Textmustern und -längen in Ihren Daten und in Ihren Abfragebegriffen. Sie müssen es so ziemlich nur ausprobieren und sehen, wie es für Sie funktioniert. Eine Sache ist, dass die GIN-Methode impg_trgm.similarity_threshold
Gegensatz zur GiST-Methode sehr empfindlich ist . Es hängt auch davon ab, welche Version von pg_trgm Sie haben. Wenn Sie mit einer älteren Version von PostgreSQL begonnen, diese jedoch aktualisiert habenpg_upgrade
, verfügen Sie möglicherweise nicht über die neueste Version. Der Planer kann nicht besser vorhersagen, welcher Indextyp überlegen ist als wir. Um es zu testen, können Sie nicht einfach beide erstellen, sondern müssen das andere löschen, um den Planer zu zwingen, das gewünschte zu verwenden.Im speziellen Fall der E-Mail-Spalte ist es möglicherweise besser, sie in Benutzername und Domain aufzuteilen und dann nach einem ähnlichen Benutzernamen mit genauer Domain abzufragen und umgekehrt. Dann ist es weniger wahrscheinlich, dass die extreme Verbreitung der großen Cloud-E-Mail-Anbieter die Indizes mit Trigrammen verschmutzt, die nur wenige Informationen hinzufügen.
Was ist der Anwendungsfall dafür? Wenn Sie wissen, warum Sie diese Abfragen ausführen müssen, können Sie bessere Vorschläge machen. Warum sollten Sie insbesondere eine Ähnlichkeitssuche für E-Mails durchführen, wenn diese als zustellbar bestätigt wurden und an die richtige Person weitergeleitet werden? Vielleicht könnten Sie einen Teilindex nur für die Teilmenge der E-Mails erstellen, die noch nicht überprüft wurden?
quelle