Agregamos dos índices pg_trgm a una tabla, para permitir la búsqueda difusa por dirección de correo electrónico o nombre, ya que necesitamos encontrar usuarios por nombre o direcciones de correo electrónico que hayan sido mal escritas durante el registro (por ejemplo, "@ gmail.con"). ANALYZE
se ejecutó después de la creación del índice.
Sin embargo, hacer una búsqueda ordenada en cualquiera de estos índices es muy lento en la gran mayoría de los casos. es decir, con un tiempo de espera incrementado, una consulta puede regresar en 60 segundos, en muy raras ocasiones tan rápido como 15 segundos, pero generalmente las consultas agotarán el tiempo de espera.
pg_trgm.similarity_threshold
es el valor predeterminado de 0.3
, pero aumentar esto a 0.8
no parece hacer la diferencia.
Esta tabla en particular tiene más de 25 millones de filas, y se consulta, actualiza e inserta constantemente (el tiempo medio para cada una es inferior a 2 ms). La configuración es PostgreSQL 9.6.6 ejecutándose en una instancia RDS db.m4.large con almacenamiento SSD de propósito general y parámetros predeterminados más o menos. La extensión pg_trgm es la versión 1.3.
Consultas:
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;
Estas consultas no necesitan ejecutarse con mucha frecuencia (docenas de veces al día), pero deben basarse en el estado actual de la tabla, y lo ideal es que regresen en unos 10 segundos.
Esquema:
=> \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
(Soy consciente de que probablemente deberíamos también añadir unaccent()
a users_search_name_idx
y la consulta de nombre ...)
Explica:
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
Es más probable que se agote el tiempo de espera de la búsqueda de correo electrónico que la búsqueda de nombre, pero probablemente se deba a que las direcciones de correo electrónico son muy similares (por ejemplo, muchas direcciones de @ 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
¿Cuál podría ser una razón para los tiempos de consulta lentos? ¿Algo relacionado con la cantidad de buffers que se leen? No pude encontrar mucha información sobre cómo optimizar este tipo particular de consulta, y las consultas son muy similares a las de la documentación de pg_trgm de todos modos.
¿Es esto algo que podríamos optimizar o implementar mejor en Postgres, o buscar algo como Elasticsearch sería mejor para este caso de uso en particular?
<->
operador que usa un índice?
pg_trgm
al menos 1.3? Puede consultar con "\ dx" enpsql
.