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 % '[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;
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 % '[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
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?
fonte
pg_trgm
pelo menos 1.3? Você pode verificar com "\ dx" empsql
.<->
operador que usa um índice?Respostas:
Você poderá obter melhor desempenho com o
gin_trgm_ops
quegist_trgm_ops
. O que é melhor é bastante imprevisível, é sensível à distribuição de padrões e comprimentos de texto em seus dados e em seus termos de consulta. Você apenas precisa experimentá-lo e ver como funciona para você. Uma coisa é que o método GIN será bastante sensívelpg_trgm.similarity_threshold
, diferentemente do método GiST. Também dependerá da versão do pg_trgm que você possui. Se você começou com uma versão mais antiga do PostgreSQL, mas a atualizoupg_upgrade
, pode não ter a versão mais recente. O planejador não se sai melhor ao prever qual tipo de índice é superior ao que podemos fazer. Portanto, para testá-lo, você não pode simplesmente criar os dois, é preciso largar o outro, para forçar o planejador a usar o desejado.No caso específico da coluna de email, é melhor dividi-los em nome de usuário e domínio e, em seguida, procurar um nome de usuário semelhante com o domínio exato e vice-versa. Então a prevalência extrema dos principais provedores de e-mail na nuvem tem menos probabilidade de poluir os índices com trigramas que adicionam pouca informação.
Finalmente, qual é o caso de uso para isso? Saber por que você precisa executar essas consultas pode levar a melhores sugestões. Em particular, por que você precisaria fazer uma pesquisa de similaridade nos e-mails, depois que eles foram verificados como entregáveis e indo para a pessoa correta? Talvez você possa criar um índice parcial apenas no subconjunto de e-mails que ainda não foram verificados?
fonte