Lenta ricerca full-text per termini con elevata occorrenza


8

Ho una tabella che contiene dati estratti da documenti di testo. I dati sono memorizzati in una colonna chiamata "CONTENT"per la quale ho creato questo indice usando GIN:

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

Uso la seguente query per eseguire una ricerca di testo completo sulla tabella:

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;

La tabella File contiene 250.000 righe e ogni "CONTENT"voce è composta da una parola casuale e una stringa di testo uguale per tutte le righe.

Ora, quando cerco una parola casuale (1 hit nell'intera tabella) la query viene eseguita molto velocemente (<100 ms). Tuttavia, quando cerco una parola presente in tutte le righe, la query è estremamente lenta (10 minuti o più).

EXPLAIN ANALYZEmostra che per la ricerca a 1 hit viene eseguita una Scansione indice bitmap seguita da una scansione heap bitmap . Per la ricerca lenta viene invece eseguita una scansione Seq , che è ciò che richiede così tanto tempo.

Certo, non è realistico avere gli stessi dati in tutte le righe. Ma dal momento che non riesco a controllare i documenti di testo che vengono caricati dagli utenti, né le ricerche che eseguono, è possibile che si verifichi uno scenario simile (ricerca di termini con ricorrenza molto elevata nel DB). Come posso aumentare le prestazioni della mia query di ricerca per un tale scenario?

Esecuzione di PostgreSQL 9.3.4

Piani di query da EXPLAIN ANALYZE:

Ricerca rapida (1 hit nel 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"

Ricerca lenta (250.000 risultati nel DB)

"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"

1
Dall'alto della mia testa: gli indici GIN hanno ricevuto importanti miglioramenti in Postgres 9.4 (e alcuni altri nel prossimo 9.5). Pagherà sicuramente per l'aggiornamento all'attuale 9.4. E vorrei anche studiare le prestazioni di GiST invece dell'indice GIN. Il colpevole della tua richiesta è ORDER BY "RANK" DESC. Vorrei investigare pg_trgmcon indice GiST e gli operatori somiglianza / a distanza come alternativa. Considerare: dba.stackexchange.com/questions/56224/… . Potrebbe anche produrre risultati "migliori" (oltre ad essere più veloce).
Erwin Brandstetter,

Su quale sistema operativo esegui l'istanza PostgreSQL?
Kassandry,

Puoi ripeterli con explain (analyze, buffers), preferibilmente con track_io_timing impostato su ON? Non è necessario impiegare 520 secondi per eseguire la scansione di tale tabella, a meno che non sia memorizzato su un RAID di floppy disk. Qualcosa è decisamente patologico lì. Inoltre, qual è la tua impostazione random_page_coste gli altri parametri di costo?
jjanes,

@danjo Sto affrontando gli stessi problemi anche quando non uso l'ordinamento. Puoi dirmi come l'hai risolto?
Sahil Bahl,

Risposte:


11

Caso d'uso discutibile

... ogni voce di CONTENUTO è composta da una parola casuale e una stringa di testo uguale per tutte le righe.

Una stringa di testo uguale per tutte le righe è solo un trasporto morto. Rimuovilo e concatenalo in una vista se devi mostrarlo.

Ovviamente, ne sei consapevole:

Certo, non è realistico ... Ma dal momento che non riesco a controllare il testo ...

Aggiorna la tua versione di Postgres

Esecuzione di PostgreSQL 9.3.4

Mentre sei ancora su Postgres 9.3, dovresti almeno aggiornare alla versione più recente (attualmente 9.3.9). La raccomandazione ufficiale del progetto:

Raccomandiamo sempre che tutti gli utenti eseguano l'ultima versione minore disponibile per qualsiasi versione principale sia in uso.

Meglio ancora, eseguire l'aggiornamento a 9.4 che ha ricevuto importanti miglioramenti per gli indici GIN .

Problema principale 1: stime dei costi

Il costo di alcune funzioni di ricerca di testo è stato gravemente sottovalutato fino alla versione 9.4 inclusa. Tale costo è aumentato del fattore 100 nella prossima versione 9.5 come descritto da @jjanes nella sua recente risposta:

Ecco il rispettivo thread in cui questo è stato discusso e il messaggio di commit di Tom Lane.

Come puoi vedere nel messaggio di commit, to_tsvector()è tra quelle funzioni. Puoi applicare la modifica immediatamente (come superutente):

ALTER FUNCTION to_tsvector (regconfig, text) COST 100;

che dovrebbe rendere molto più probabile l'utilizzo dell'indice funzionale.

Problema principale 2: KNN

Il problema principale è che Postgres deve calcolare una classifica con ts_rank()260k righe ( rows=261011) prima di poter ordinare e scegliere la top 5. Questo sarà costoso , anche dopo aver risolto altri problemi come discusso. È un problema K-più vicino-vicino (KNN) per natura e ci sono soluzioni per casi correlati. Ma non riesco a pensare a una soluzione generale per il tuo caso, poiché il calcolo del rango stesso dipende dall'input dell'utente. Vorrei provare a eliminare la maggior parte delle partite di basso livello in anticipo in modo che il calcolo completo debba essere fatto solo per pochi buoni candidati.

Un modo in cui riesco a pensare è quello di combinare la ricerca full-text con la ricerca della somiglianza del trigramma , che offre un'implementazione funzionante per il problema KNN. In questo modo è possibile preselezionare le corrispondenze "migliori" con LIKEpredicato come candidati (in una sottoquery con LIMIT 50ad esempio) e quindi selezionare le 5 righe di primo livello in base al calcolo del grado nella query principale.

Oppure applica entrambi i predicati nella stessa query e scegli le corrispondenze più vicine in base alla somiglianza del trigramma (che produrrebbe risultati diversi) come in questa risposta correlata:

Ho fatto qualche ricerca in più e non sei il primo a riscontrare questo problema. Articoli correlati su pgsql-general:

Sono in corso lavori per implementare un tsvector <-> tsqueryoperatore.

Oleg Bartunov e Alexander Korotkov hanno persino presentato un prototipo funzionante (usando ><come operatore invece di <->allora), ma è molto complesso da integrare in Postgres, l'intera infrastruttura per gli indici GIN deve essere rielaborata (la maggior parte dei quali è stata fatta ormai).

Problema principale 3: pesi e indice

E ho identificato un ulteriore fattore che si aggiunge alla lentezza della query. Per documentazione:

Gli indici GIN non sono in perdita per le query standard, ma le loro prestazioni dipendono logaritmicamente dal numero di parole uniche. ( Tuttavia, gli indici GIN memorizzano solo le parole (lexemi) dei tsvectorvalori e non le loro etichette di peso. Pertanto, quando si utilizza una query che coinvolge pesi è necessario un nuovo controllo della riga della tabella.)

Enorme enfasi sulla mia. Non appena viene coinvolto il peso, ogni riga deve essere recuperata dall'heap (non solo un controllo di visibilità economico) e i valori lunghi devono essere tostati, il che aumenta il costo. Ma sembra esserci una soluzione per questo:

Definizione dell'indice

Guardando di nuovo il tuo indice, non sembra logico cominciare. Si assegna un peso a una singola colonna, che è insignificante purché non si concatenino altre colonne con un peso diverso .

COALESCE() inoltre, non ha senso se non concateni più colonne.

Semplifica il tuo indice:

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

E la tua query:

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;

Ancora costoso per un termine di ricerca che corrisponde a ogni riga, ma probabilmente molto meno.

asides

Tutti questi problemi combinati, il folle costo di 520 secondi per la tua seconda query sta iniziando a dare un senso. Ma potrebbero esserci ancora più problemi. Hai configurato il tuo server?
Si applicano tutti i consueti consigli per l'ottimizzazione delle prestazioni.

Ti semplifica la vita se non lavori con identificatori di casi CaMeL con virgolette doppie:


Ci sto anche incontrando. Con Postgresql 9.6, utilizziamo la riscrittura delle query per sinonimi, quindi non credo che l'utilizzo della ricerca di somiglianza trigram per limitare il numero di documenti funzionerà bene.
pholly,

Sorprendente! USING gin (to_tsvector('english', "CONTENT")
K-Gun

1

Ho avuto un problema simile. Mi sono preso cura di esso precompilando lo ts_rank di ogni termine di query di testo popolare rispetto a un campo: tupla di tabella e memorizzandolo in una tabella di ricerca. Questo mi ha fatto risparmiare un sacco di tempo (fattore 40X) durante la ricerca di parole popolari nel corpus pesante del testo.

  1. Ottieni parole popolari nel corpus scansionando il documento e contando le sue occorrenze.
  2. Ordina per parola più popolare.
  3. precompute ts_rank delle parole popolari e memorizzalo in una tabella.

Query: cercare questa tabella e ottenere gli ID documento ordinati in base al rispettivo rango. se non lì, fallo alla vecchia maniera.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.