Otimizando ORDER BY em uma consulta de pesquisa de texto completo

8

Eu tenho uma mesa grande entitiescom ~ 15 milhões de registros. Eu quero encontrar as 5 principais linhas correspondentes a 'hockey' na sua name.

Eu tenho um índice de texto completo name, usado:gin_ix_entity_full_text_search_name

Inquerir:

 SELECT "entities".*,
         ts_rank(to_tsvector('english', "entities"."name"::text),
         to_tsquery('english', 'hockey'::text)) AS "rank0.48661998202865475"
    FROM "entities" 
         WHERE "entities"."place" = 'f' 
              AND (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'hockey'::text)) 
         ORDER BY "rank0.48661998202865475" DESC LIMIT 5

Duração 25.623 ms

Explique o plano
1 Limite (custo = 12666,89..12666,89 linhas = 5 largura = 3116)
2 -> Classificação (custo = 12666,89..12670,18 linhas = 6571 largura = 3116)
3 Chave de Classificação: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' hockey '' '' :: tsquery)))
4 -> Verificação de heap de bitmap em entidades (custo = 124.06..12645.06 linhas = 6571 width = 3116)
5 Verifique novamente Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)
6 Filtro: (NÃO coloque)
7 -> Varredura de índice de bitmap no gin_ix_entity_full_text_search_name (custo = 0.00..123.74 linhas = 6625 width = 0)
8 Índice Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)

Não entendo por que ele verifica a condição do índice duas vezes. (Etapa 4 e 7 do plano de consulta). É por causa da minha condição booleana ( not place)? Em caso afirmativo, devo adicioná-lo ao meu índice para obter uma consulta muito rápida? Ou a condição de classificação torna lenta?

EXPLAIN ANALYZE resultado:

  Limite (custo = 4447,28..4447,29 linhas = 5 largura = 3116) (tempo real = 18509,274..18509,282 linhas = 5 loops = 1)
  -> Classificação (custo = 4447,28..4448,41 linhas = 2248 largura = 3116) (tempo real = 18509,271..18509,273 linhas = 5 loops = 1)
         Chave de classificação: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' test '' ':: :: tsquery)))
         Método de classificação: top-N heapsort Memória: 19kB
     -> Varredura de Heap de Bitmap em entidades (custo = 43.31..4439.82 linhas = 2248 largura = 3116) (tempo real = 119.003..18491.408 linhas = 2533 loops = 1)
           Verifique novamente Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery)
           Filtro: (NÃO colocar)
           -> Varredura de índice de bitmap no gin_ix_entity_full_text_search_name (custo = 0.00..43.20 linhas = 2266 width = 0) (tempo real = 74.093..74.093 linhas = 2593 loops = 1)
                 Índice Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery)
 Tempo de execução total: 18509,381 ms

Aqui estão meus parâmetros de banco de dados. Está hospedado em Heroku, nos serviços da Amazon. Eles o descrevem como tendo 1,7 GB de RAM, 1 unidade de processamento e um banco de dados de no máximo 1 TB.

nome | Configuração atual
------------------------------ + ------------------- -------------------------------------------------- ------------------------------------
 versão | PostgreSQL 9.0.7 no i486-pc-linux-gnu, compilado pelo GCC gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3, 32 bits
 arquivo_command | teste -f /etc/postgresql/9.0/main/wal-ed/ARCHIVING_OFF || envdir /etc/postgresql/9.0/resource29857_heroku_com/wal-ed/env wal-e wal-push% p
 archive_mode | em
 archive_timeout | 1 minuto
 checkpoint_completion_target | 0,7
 checkpoint_segments | 40.
 client_min_messages | aviso prévio
 cpu_index_tuple_cost | 0,001
 cpu_operator_cost | 0,0005
 cpu_tuple_cost | 0,003
 effective_cache_size | 1530000kB
 hot_standby | em
 lc_collate | en_US.UTF-8
 lc_ctype | en_US.UTF-8
 listen_addresses | *
 log_checkpoints | em
 log_destination | syslog
 log_line_prefix | % u [AMARELO]
 log_min_duration_statement | 50ms
 log_min_messages | aviso prévio
 logging_collector | em
 maintenance_work_mem | 64MB
 max_connections | 500
 max_prepared_transactions | 500
 max_stack_depth | 2MB
 max_standby_archive_delay | -1
 max_standby_streaming_delay | -1
 max_wal_senders | 10
 porto | 
 random_page_cost | 2
 server_encoding | UTF8
 shared_buffers | 415MB
 ssl em
 syslog_ident | resource29857_heroku_com
 Fuso horário | UTC
 wal_buffers | 8MB
 wal_keep_segments | 127
 wal_level | Hot Standby
 work_mem | 100MB
 (39 linhas)

EDITAR

Parece que ORDER BYé a parte mais lenta:

d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
     ts_rank(to_tsvector('english', "entities"."name"::text),
     to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities" 
     WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text)) 
     LIMIT 5;

QUERY PLAN                                                                         
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=43.31..53.07 rows=5 width=24) (actual time=76.583..103.623 rows=5 loops=1)
->  Bitmap Heap Scan on entities  (cost=43.31..4467.60 rows=2266 width=24) (actual time=76.581..103.613 rows=5 loops=1)
     Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
     ->  Bitmap Index Scan on gin_ix_entity_full_text_search_name  (cost=0.00..43.20 rows=2266 width=0) (actual time=53.592..53.592 rows=1495 loops=1)
           Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
 Total runtime: 103.680 ms

Vs. com ORDER BY:

d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
     ts_rank(to_tsvector('english', "entities"."name"::text),
     to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities" 
     WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text)) 
     ORDER BY "rank0.48661998202865475" DESC
     LIMIT 5;

QUERY PLAN                                                                           
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit  (cost=4475.12..4475.13 rows=5 width=24) (actual time=15013.735..15013.741 rows=5 loops=1)
->  Sort  (cost=4475.12..4476.26 rows=2266 width=24) (actual time=15013.732..15013.735 rows=5 loops=1)
     Sort Key: (ts_rank(to_tsvector('english'::regconfig, (name)::text), '''banana'''::tsquery))
     Sort Method:  top-N heapsort  Memory: 17kB
     ->  Bitmap Heap Scan on entities  (cost=43.31..4467.60 rows=2266 width=24) (actual time=0.872..15006.763 rows=1495 loops=1)
           Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
           ->  Bitmap Index Scan on gin_ix_entity_full_text_search_name  (cost=0.00..43.20 rows=2266 width=0) (actual time=0.549..0.549 rows=1495 loops=1)
                 Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
 Total runtime: 15013.805 ms

Ainda não entendi por que isso é mais lento. Parece que está buscando a mesma quantidade de linhas do Bitmap Heap Scan, mas leva muito mais tempo?

xlash
fonte
Se o aumento do work_mem não fornecer um aumento suficiente no desempenho, mostre os resultados de EXPLAIN ANALYZE em vez de apenas EXPLAIN. Também ajuda a mostrar os resultados da execução da consulta nesta página: wiki.postgresql.org/wiki/Server_Configuration Uma breve descrição do hardware também ajuda.
kgrittn
Aqui está outra EXPLAIN ANALYZE: (ver edição no meu post acima)
xlash
Devo salientar que a configuração do PostgreSQL que você mostra provavelmente não está nem perto do ideal. Isso é fora do tópico o suficiente para que provavelmente deva ser abordado em uma pergunta separada. Eu recomendo que você faça isso.
precisa saber é o seguinte
Se você está tendo problemas para entender sua saída do EXPLAIN ANALYZE, existe uma página do Wiki que deve ajudar: wiki.postgresql.org/wiki/Using_EXPLAIN Muitas pessoas acham a página do explic.depesz.com útil; convém procurar a ajuda e experimentá-la.
kgrittn

Respostas:

8

O que ainda não entendo é por que isso é mais lento.

Que classificar as linhas custará algo é óbvio. Mas por que tanto?
Sem o ORDER BY rank0...Postgres pode apenas

  • escolha as 5 primeiras linhas que encontrar e pare de buscar linhas assim que tiver 5.

    Verificação de heap de bitmap em entidades ... linhas = 5 ...

  • depois calcule ts_rank()apenas 5 linhas.
No segundo caso, o Postgres precisa

  • busque todas as linhas (1495 de acordo com seu plano de consulta) que se qualifiquem.

    Verificação de heap de bitmap em entidades ... linhas = 1495 ...

  • calcular ts_rank()para todos eles.
  • classifique todos eles para encontrar os 5 primeiros de acordo com o valor calculado.
Tente ORDER BY nameapenas ver o custo da computação to_tsquery('english', 'hockey'::text))para as linhas supérfluas e quanto resta para buscar mais linhas e classificar.

Erwin Brandstetter
fonte
O armazenamento em cache está no caminho ... praticamente dá um desempenho tão ruim. 10seg 1500 linhas. Eu entendo sua explicação. Faz sentido. Mas enquanto fazia uma pesquisa de texto ... alguma maneira de criar meu índice para uma classificação de qualidade adequada sem extrair tudo?
xlash
5

Uma verificação de bitmap funciona assim: O índice é verificado para encontrar os locais das tuplas que correspondem às condições do índice. O bitmap pode passar por uma combinação lógica com os resultados de outras varreduras de bitmap, usando lógica booleana nos bits. Em seguida, as páginas contendo os dados são acessadas em ordem de número de página, para reduzir o acesso ao disco e, esperamos, transformar algumas leituras aleatórias em leituras seqüenciais.

Uma verificação de índice de bitmap pode precisar se tornar "com perda" para caber na memória - reduz sua precisão do nível da tupla para o nível da página. Se você aumentar work_mem (pelo menos para essa consulta), poderá evitar isso. Não será possível ignorar a verificação de heap de bitmap na linha 4 ou o filtro na linha 6, mas poderá ignorar a verificação novamente na linha 5.

kgrittn
fonte
11
Aumentado de 100 MB para 650 MB, não deu diferença de desempenho. (work_mem)
xlash 13/04/12
5

Como a pergunta editada faz parecer que o objetivo é escolher algumas das principais correspondências, em vez de uma lista de tudo que corresponda, estou postando uma resposta separada para isso, pois é um problema bastante diferente.

Convém considerar um índice KNN - GiST (para K vizinho mais próximo - Árvore de pesquisa generalizada), que pode ser extraído diretamente do índice em ordem de similaridade - para que você não precise ler aleatoriamente todas as tuplas de heap e classificar para baixo para encontrar as melhores combinações de K.

Até a presente data, acho que ninguém implementou o suporte KNN-GIST para consultas tsearch (embora tenha sido garantido que isso pode ser feito, é apenas uma questão de alguém ter tempo para fazê-lo), mas talvez o suporte ao trigrama (que é feito) irá trabalhar para a sua aplicação. A principal diferença é que as pesquisas de trigramas não usam dicionários para derivar e sinônimos da mesma forma que o tsearch; você encontrará apenas correspondências exatas das palavras.

Para experimentar trigramas para o seu exemplo, você provavelmente deseja indexar "nome" assim:

CREATE INDEX entities_name_trgm ON entities USING gist (name gist_trgm_ops);

Então você pode pesquisar assim:

SELECT
    *,
    name <-> 'banana' AS sim
  FROM entities 
  WHERE name % 'banana'
  ORDER BY sim DESC
  LIMIT 5;

Observe os operadores utilizados e o ORDER BYuso do alias da coluna "similaridade". Eu não me afastaria muito desse padrão ao testá-lo. O índice no tsvector não é usado para esta pesquisa.

Exceto problemas com sua configuração atual (que poderia facilmente lançar toda a sua VM na paginação sem esperança da superalocação de memória), você provavelmente gostará muito do desempenho disso. Se ele tem o comportamento que você quer, é o que eu não sei.

kgrittn
fonte