Nous avons ajouté deux indices pg_trgm à une table, pour permettre une recherche floue par adresse e-mail ou par nom, car nous devons trouver les utilisateurs par nom ou par adresses e-mail qui ont été mal orthographiées lors de l'inscription (par exemple, "@ gmail.con"). ANALYZE
a été exécuté après la création de l'index.
Cependant, la recherche classée sur l'un ou l'autre de ces indices est très lente dans la grande majorité des cas. c'est-à-dire qu'avec un délai d'attente plus long, une requête peut revenir en 60 secondes, en de très rares occasions aussi rapidement que 15 secondes, mais généralement les requêtes expirent.
pg_trgm.similarity_threshold
est la valeur par défaut de 0.3
, mais cela 0.8
ne semble pas faire de différence.
Ce tableau particulier compte plus de 25 millions de lignes et est constamment interrogé, mis à jour et inséré dans (le temps moyen pour chacun est inférieur à 2 ms). La configuration est PostgreSQL 9.6.6 s'exécutant sur une instance RDS db.m4.large avec un stockage SSD à usage général et des paramètres par défaut plus ou moins. L'extension pg_trgm est la version 1.3.
Requêtes:
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;
Ces requêtes n'ont pas besoin d'être exécutées très souvent (des dizaines de fois par jour), mais elles doivent être basées sur l'état actuel de la table et idéalement renvoyer dans un délai d'environ 10 secondes.
Schéma:
=> \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
(Je suis conscient que nous devrions probablement aussi ajouter unaccent()
à users_search_name_idx
et la requête de nom ...)
Explique:
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
La recherche par e-mail est plus susceptible de se terminer que la recherche par nom, mais c'est probablement parce que les adresses e-mail sont très similaires (par exemple, beaucoup d'adresses @ 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
Quelle pourrait être la raison de la lenteur des requêtes? Quelque chose à voir avec le nombre de tampons lus? Je n'ai pas pu trouver beaucoup d'informations sur l'optimisation de ce type particulier de requête, et les requêtes sont très similaires à celles de la documentation pg_trgm de toute façon.
Est-ce quelque chose que nous pourrions optimiser, ou mieux implémenter dans Postgres, ou envisager quelque chose comme Elasticsearch serait-il mieux adapté à ce cas d'utilisation particulier?
<->
opérateur qui utilise un index?
pg_trgm
au moins 1.3? Vous pouvez vérifier avec "\ dx" danspsql
.