Abbiamo aggiunto due indici pg_trgm a una tabella, per consentire la ricerca fuzzy per indirizzo e-mail o nome, poiché dobbiamo trovare utenti per nome o indirizzi e-mail che sono stati erroneamente registrati durante la registrazione (ad esempio "@ gmail.con"). ANALYZE
è stato eseguito dopo la creazione dell'indice.
Tuttavia, fare una ricerca classificata su uno di questi indici è molto lento nella stragrande maggioranza dei casi. ad esempio, con un timeout maggiore, una query potrebbe tornare in 60 secondi, in occasioni molto rare fino a 15 secondi, ma di solito le query scadranno.
pg_trgm.similarity_threshold
è il valore predefinito di 0.3
, ma aumentarlo fino a 0.8
che non sembra fare la differenza.
Questa particolare tabella ha oltre 25 milioni di righe ed è costantemente interrogata, aggiornata e inserita (il tempo medio per ciascuna è inferiore a 2 ms). L'impostazione è PostgreSQL 9.6.6 in esecuzione su un'istanza RDS db.m4.large con memoria SSD per scopi generici e parametri predefiniti più o meno. L'estensione pg_trgm è la versione 1.3.
Interrogazioni:
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;
Queste query non devono essere eseguite molto spesso (dozzine di volte al giorno), ma dovrebbero essere basate sullo stato corrente della tabella e idealmente restituire entro circa 10 secondi.
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
(Mi rendo conto che probabilmente dovremmo anche aggiungere unaccent()
al users_search_name_idx
e la query nome ...)
spiega:
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
È più probabile che la ricerca e-mail scada rispetto alla ricerca per nome, ma presumibilmente perché gli indirizzi e-mail sono così simili (ad esempio molti indirizzi @ 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
Quale potrebbe essere una ragione per i tempi di query lenti? Qualcosa a che fare con il numero di buffer letti? Non sono riuscito a trovare molte informazioni sull'ottimizzazione di questo particolare tipo di query e le query sono comunque molto simili a quelle contenute nella documentazione di pg_trgm.
È qualcosa che potremmo ottimizzare, o implementare meglio in Postgres, o cercare qualcosa come Elasticsearch potrebbe adattarsi meglio a questo particolare caso d'uso?
<->
operatore che utilizza un indice?
pg_trgm
almeno 1.3? Puoi controllare con "\ dx" inpsql
.