Adicionamos dois índices pg_trgm a uma tabela, para permitir a pesquisa difusa por endereço de email ou nome, pois precisamos encontrar usuários por nome ou endereços de email que foram digitados incorretamente durante a inscrição (por exemplo, "@ gmail.con"). ANALYZE
foi executado após a criação do índice.
No entanto, fazer uma pesquisa classificada em qualquer um desses índices é muito lento na grande maioria dos casos. ou seja, com um tempo limite aumentado, uma consulta pode retornar em 60 segundos, em ocasiões muito raras, em apenas 15 segundos, mas geralmente as consultas expiram.
pg_trgm.similarity_threshold
é o valor padrão de 0.3
, mas aumentar 0.8
isso não parece fazer a diferença.
Essa tabela específica possui mais de 25 milhões de linhas e é constantemente consultada, atualizada e inserida (o tempo médio para cada uma é inferior a 2ms). A configuração é o PostgreSQL 9.6.6 em execução em uma instância do RDS db.m4.large com armazenamento SSD de uso geral e parâmetros padrão mais ou menos. A extensão pg_trgm é a versão 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;
Essas consultas não precisam ser executadas com muita frequência (dezenas de vezes por dia), mas devem ser baseadas no estado atual da tabela e, idealmente, retornar em cerca de 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
(Estou ciente de que nós provavelmente deve também adicionar unaccent()
a users_search_name_idx
e a consulta de nome ...)
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
É mais provável que a pesquisa por e-mail atinja o tempo limite do que a pesquisa por nome, mas provavelmente isso ocorre porque os endereços de e-mail são muito semelhantes (por exemplo, muitos endereços @ 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
Qual poderia ser um motivo para os tempos de consulta lentos? Algo a ver com o número de buffers sendo lidos? Não consegui encontrar muita informação sobre como otimizar esse tipo específico de consulta, e as consultas são muito semelhantes às da documentação do pg_trgm.
Isso é algo que poderíamos otimizar ou implementar melhor no Postgres, ou considerar algo como o Elasticsearch seria mais adequado para esse caso de uso específico?
<->
operador que usa um índice?
pg_trgm
pelo menos 1.3? Você pode verificar com "\ dx" empsql
.