Pesquisa lenta de texto completo para termos com alta ocorrência

8

Eu tenho uma tabela que contém dados extraídos de documentos de texto. Os dados são armazenados em uma coluna chamada "CONTENT"para a qual eu criei esse índice usando o GIN:

CREATE INDEX "File_contentIndex"
  ON "File"
  USING gin
  (setweight(to_tsvector('english'::regconfig
           , COALESCE("CONTENT", ''::character varying)::text), 'C'::"char"));

Eu uso a seguinte consulta para executar uma pesquisa de texto completo na tabela:

SELECT "ITEMID",
  ts_rank(setweight(to_tsvector('english', coalesce("CONTENT",'')), 'C') , 
  plainto_tsquery('english', 'searchTerm')) AS "RANK"
FROM "File"
WHERE setweight(to_tsvector('english', coalesce("CONTENT",'')), 'C') 
  @@ plainto_tsquery('english', 'searchTerm')
ORDER BY "RANK" DESC
LIMIT 5;

A tabela Arquivo contém 250 000 linhas e cada "CONTENT"entrada consiste em uma palavra aleatória e uma sequência de texto que é igual para todas as linhas.

Agora, quando procuro uma palavra aleatória (1 acerto na tabela inteira), a consulta é executada muito rapidamente (<100 ms). No entanto, quando procuro uma palavra que esteja presente em todas as linhas, a consulta é extremamente lenta (10 minutos ou mais).

EXPLAIN ANALYZEmostra que, para a pesquisa de um clique, é realizada uma verificação de índice de bitmap seguida de uma verificação de heap de bitmap . Para a pesquisa lenta, é realizada uma Seq Scan , que é o que está demorando tanto.

Concedido, não é realista ter os mesmos dados em todas as linhas. Mas como não consigo controlar os documentos de texto enviados pelos usuários, nem as pesquisas que eles realizam, é possível que ocorra um cenário semelhante (pesquise termos com ocorrência muito alta no DB). Como posso aumentar o desempenho da minha consulta de pesquisa para esse cenário?

Executando o PostgreSQL 9.3.4

Planos de consulta de EXPLAIN ANALYZE:

Pesquisa rápida (1 hit no DB)

"Limit  (cost=2802.89..2802.90 rows=5 width=26) (actual time=0.037..0.037 rows=1 loops=1)"
"  ->  Sort  (cost=2802.89..2806.15 rows=1305 width=26) (actual time=0.037..0.037 rows=1 loops=1)"
"        Sort Key: (ts_rank(setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char"), '''wfecg'''::tsquery))"
"        Sort Method: quicksort  Memory: 25kB"
"        ->  Bitmap Heap Scan on "File"  (cost=38.12..2781.21 rows=1305 width=26) (actual time=0.030..0.031 rows=1 loops=1)"
"              Recheck Cond: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''wfecg'''::tsquery)"
"              ->  Bitmap Index Scan on "File_contentIndex"  (cost=0.00..37.79 rows=1305 width=0) (actual time=0.012..0.012 rows=1 loops=1)"
"                    Index Cond: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''wfecg'''::tsquery)"
"Total runtime: 0.069 ms"

Pesquisa lenta (250k ocorrências no banco de dados)

"Limit  (cost=14876.82..14876.84 rows=5 width=26) (actual time=519667.404..519667.405 rows=5 loops=1)"
"  ->  Sort  (cost=14876.82..15529.37 rows=261017 width=26) (actual time=519667.402..519667.402 rows=5 loops=1)"
"        Sort Key: (ts_rank(setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char"), '''cyberspace'''::tsquery))"
"        Sort Method: top-N heapsort  Memory: 25kB"
"        ->  Seq Scan on "File"  (cost=0.00..10541.43 rows=261017 width=26) (actual time=2.097..519465.953 rows=261011 loops=1)"
"              Filter: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''cyberspace'''::tsquery)"
"              Rows Removed by Filter: 6"
"Total runtime: 519667.429 ms"
danjo
fonte
11
Do alto da minha cabeça: os índices GIN receberam grandes melhorias no Postgres 9.4 (e mais algumas nas próximas 9.5). Certamente pagará para atualizar para o 9.4 atual. E eu também investigaria o desempenho do GiST em vez do índice GIN. O culpado em sua consulta é ORDER BY "RANK" DESC. Eu investigaria pg_trgmcom o índice GiST e os operadores de similaridade / distância como alternativa. Considere: dba.stackexchange.com/questions/56224/… . Pode até produzir resultados "melhores" (além de serem mais rápidos).
Erwin Brandstetter
Em qual SO você está executando sua instância do PostgreSQL?
Kassandry #
Você pode repeti-los com explain (analyze, buffers), de preferência com track_io_timing definido como ON? Não há como levar 520 segundos para verificar seq essa tabela, a menos que você a armazene em um RAID de disquetes. Algo é definitivamente patológico lá. Além disso, qual é a sua configuração random_page_coste os outros parâmetros de custo?
jjanes
@danjo Estou enfrentando os mesmos problemas, mesmo quando não uso os pedidos. Você pode me dizer como consertou?
Sahil Bahl

Respostas:

11

Caso de uso questionável

... cada entrada de CONTEÚDO consiste em uma palavra aleatória e uma sequência de texto que é igual para todas as linhas.

Uma sequência de texto que é a mesma para todas as linhas é apenas frete morto. Remova-o e concatene-o em uma exibição, se precisar mostrá-lo.

Obviamente, você está ciente disso:

É verdade que não é realista ... Mas como não consigo controlar o texto ...

Atualize sua versão do Postgres

Executando o PostgreSQL 9.3.4

Enquanto ainda estiver no Postgres 9.3, você deve pelo menos atualizar para a versão mais recente do ponto (atualmente 9.3.9). A recomendação oficial do projeto:

Sempre recomendamos que todos os usuários executem a última versão menor disponível para qualquer versão principal em uso.

Melhor ainda, atualize para a 9.4, que recebeu grandes melhorias nos índices GIN .

Problema principal 1: estimativas de custo

O custo de algumas funções de pesquisa de texto foi seriamente subestimado até e incluindo a versão 9.4. Esse custo é aumentado pelo fator 100 na próxima versão 9.5, como @jjanes descreve em sua resposta recente:

Aqui estão os respectivos tópicos em que isso foi discutido e a mensagem de confirmação de Tom Lane.

Como você pode ver na mensagem de confirmação, to_tsvector()está entre essas funções. Você pode aplicar a alteração imediatamente (como superusuário):

ALTER FUNCTION to_tsvector (regconfig, text) COST 100;

o que deve tornar muito mais provável que seu índice funcional seja usado.

Grande problema 2: KNN

O principal problema é que o Postgres precisa calcular uma classificação com ts_rank()260k linhas ( rows=261011) antes de poder solicitar e selecionar as cinco principais. Isso vai ser caro , mesmo depois que você tiver corrigido outros problemas, conforme discutido. É um problema K-vizinho mais próximo (KNN) por natureza e existem soluções para casos relacionados. Mas não consigo pensar em uma solução geral para o seu caso, pois o próprio cálculo de classificação depende da entrada do usuário. Eu tentaria eliminar antecipadamente a maior parte das correspondências de baixa classificação, para que o cálculo completo só seja feito para poucos bons candidatos.

Uma maneira que eu posso pensar é combinar a sua pesquisa de texto completo com trigrama pesquisa por similaridade - que oferece uma implementação de trabalho para o problema KNN. Dessa forma, você pode pré-selecionar as "melhores" correspondências com LIKEpredicado como candidatos (em uma subconsulta com, LIMIT 50por exemplo) e, em seguida, escolher as 5 linhas de classificação superior de acordo com o cálculo de classificação na consulta principal.

Ou aplique os dois predicados na mesma consulta e escolha as correspondências mais próximas de acordo com a semelhança do trigrama (que produziria resultados diferentes), como nesta resposta relacionada:

Eu fiz mais algumas pesquisas e você não é o primeiro a encontrar esse problema. Posts relacionados no pgsql-general:

Estão em andamento trabalhos para eventualmente implementar um tsvector <-> tsqueryoperador.

Oleg Bartunov e Alexander Korotkov chegaram a apresentar um protótipo funcional (usando ><como operador em vez de <->na época), mas é muito complexo integrar-se ao Postgres, toda a infraestrutura para os índices GIN precisa ser reformulada (a maioria já é feita agora).

Grande problema 3: pesos e índice

E identifiquei mais um fator a ser adicionado à lentidão da consulta. Por documentação:

Os índices GIN não apresentam perdas para consultas padrão, mas seu desempenho depende logaritmicamente do número de palavras exclusivas. ( No entanto, os índices GIN armazenam apenas as palavras (lexemes) dos tsvectorvalores, e não seus rótulos de peso. Portanto, é necessário verificar novamente a linha da tabela ao usar uma consulta que envolva pesos.)

Negrito ênfase minha. Assim que o peso é envolvido, cada linha precisa ser buscada da pilha (não apenas uma verificação de visibilidade barata) e valores longos devem ser retirados da torrada, o que aumenta o custo. Mas parece haver uma solução para isso:

Definição de índice

Olhando para o seu índice novamente, não parece fazer sentido, para começar. Você atribui um peso a uma única coluna, que não faz sentido , desde que não concatenar outras colunas com um peso diferente .

COALESCE() também não faz sentido, desde que você não concatene mais colunas.

Simplifique seu índice:

CREATE INDEX "File_contentIndex" ON "File" USING gin
(to_tsvector('english', "CONTENT");

E sua consulta:

SELECT "ITEMID", ts_rank(to_tsvector('english', "CONTENT")
                       , plainto_tsquery('english', 'searchTerm')) AS rank
FROM   "File"
WHERE  to_tsvector('english', "CONTENT")
       @@ plainto_tsquery('english', 'searchTerm')
ORDER  BY rank DESC
LIMIT  5;

Ainda caro para um termo de pesquisa que corresponda a todas as linhas, mas provavelmente muito menos.

Apartes

Todos esses problemas combinados, o custo insano de 520 segundos para sua segunda consulta está começando a fazer sentido. Mas ainda pode haver mais problemas. Você configurou seu servidor?
Todos os conselhos usuais para otimização de desempenho se aplicam.

Facilita sua vida se você não trabalha com identificadores de caso CaMeL com aspas duplas:

Erwin Brandstetter
fonte
Eu também estou encontrando isso. Com o Postgresql 9.6, usamos a reescrita de consultas para sinônimos, então não acho que usar a pesquisa de similaridade de trigramas para limitar o número de documentos funcione bem.
Phll 9/17
Surpreendente! USING gin (to_tsvector('english', "CONTENT")
K-Gun
1

Eu tive um problema semelhante. Eu cuidei disso pré-computando o ts_rank de cada termo popular de consulta de texto em um campo: tupla de tabela e armazenando-o em uma tabela de pesquisa. Isso me salvou muito tempo (fator 40X) durante a pesquisa de palavras populares no corpus pesado do texto.

  1. Obtenha palavras populares no corpus, digitalizando o documento e contando suas ocorrências.
  2. Classifique pela palavra mais popular.
  3. precompute ts_rank das palavras populares e armazene-o em uma tabela.

Consulta: consulte esta tabela e obtenha os IDs de documentos classificados por sua respectiva classificação. se não estiver lá, faça da maneira antiga.

Pari Rajaram
fonte