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 % '[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;
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 % '[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
¿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?
fuente
pg_trgm
al menos 1.3? Puede consultar con "\ dx" enpsql
.<->
operador que usa un índice?Respuestas:
Es posible que pueda obtener un mejor rendimiento en
gin_trgm_ops
lugar degist_trgm_ops
. Lo que es mejor es bastante impredecible, es sensible a la distribución de patrones de texto y longitudes en sus datos y en sus términos de consulta. Simplemente tienes que probarlo y ver cómo funciona para ti. Una cosa es que el método GIN será bastante sensiblepg_trgm.similarity_threshold
, a diferencia del método GiST. También dependerá de la versión de pg_trgm que tenga. Si comenzó con una versión anterior de PostgreSQL pero la actualizópg_upgrade
, es posible que no tenga la última versión. El planificador no puede predecir mejor qué tipo de índice es superior al que podemos hacer. Entonces, para probarlo, no puedes simplemente crear ambos, tienes que soltar el otro, para forzar al planificador a usar el que quieras.En el caso específico de la columna de correo electrónico, puede ser mejor dividirlos en nombre de usuario y dominio, y luego buscar un nombre de usuario similar con un dominio exacto y viceversa. Entonces, la prevalencia extrema de los principales proveedores de correo electrónico en la nube es menos probable que contamine los índices con trigramas que agregan poca información.
Finalmente, ¿cuál es el caso de uso para esto? Saber por qué necesita ejecutar estas consultas podría conducir a mejores sugerencias. En particular, ¿por qué necesitaría hacer una búsqueda de similitud en los correos electrónicos, una vez que se haya verificado que se pueden entregar e ir a la persona correcta? ¿Quizás podría crear un índice parcial solo en el subconjunto de correos electrónicos que aún no se han verificado?
fuente