Ottimizzazione di ORDER BY in una query di ricerca full-text


8

Ho un grande tavolo entitiescon ~ 15 milioni di record. Voglio trovare le prime 5 righe corrispondenti a "hockey" nelle loro name.

Ho un indice di testo completo su name, che viene utilizzato:gin_ix_entity_full_text_search_name

Query:

 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

Durata 25.623 ms

Spiega il piano
1 limite (costo = 12666,89..12666,89 righe = 5 larghezza = 3116)
2 -> Ordina (costo = 12666,89..12670,18 righe = 6571 larghezza = 3116)
3 Chiave di ordinamento: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'hockey' '' :: tsquery))
4 -> Scansione heap bitmap su entità (costo = 124.06..12645.06 righe = 6571 larghezza = 3116)
5 Ricontrolla Cond: (to_tsvector ('inglese' :: regconfig, (nome) :: testo) @@ '' 'hockey' '' :: tsquery)
6 Filtro: (NON posizionare)
7 -> Scansione indice bitmap su gin_ix_entity_full_text_search_name (costo = 0,00..123,74 righe = 6625 larghezza = 0)
8 Index Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)

Non capisco perché verifica due volte la condizione dell'indice. (Piano di query passaggi 4 e 7). È a causa della mia condizione booleana ( not place)? In tal caso, dovrei aggiungerlo al mio indice per ottenere una query molto veloce? O la condizione di smistamento lo rallenta?

EXPLAIN ANALYZE produzione:

  Limite (costo = 4447.28..4447.29 righe = 5 larghezza = 3116) (tempo effettivo = 18509.274..18509.282 righe = 5 anelli = 1)
  -> Ordina (costo = 4447.28..4448.41 righe = 2248 larghezza = 3116) (tempo effettivo = 18509.271..18509.273 righe = 5 loop = 1)
         Chiave di ordinamento: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'test' '' :: tsquery))
         Metodo di ordinamento: memoria heapsort top-N: 19kB
     -> Scansione heap bitmap su entità (costo = 43.31..4439.82 righe = 2248 larghezza = 3116) (tempo effettivo = 119.003..18491.408 righe = 2533 loop = 1)
           Ricontrolla Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery)
           Filtro: (NON posizionare)
           -> Scansione indice bitmap su gin_ix_entity_full_text_search_name (costo = 0,00..43,20 righe = 2266 larghezza = 0) (tempo effettivo = 74.093..74.093 righe = 2593 loop = 1)
                 Index Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery)
 Durata totale: 18509.381 ms

Ecco i miei parametri DB. È ospitato da Heroku, sui servizi di Amazon. Lo descrivono con 1,7 GB di RAM, 1 unità di elaborazione e un DB di massimo 1 TB.

nome | impostazioni correnti
------------------------------ + ------------------- -------------------------------------------------- ------------------------------------
 versione | PostgreSQL 9.0.7 su i486-pc-linux-gnu, compilato da GCC gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3, 32-bit
 comando_archivio | test -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
 modalità_archivio | su
 archive_timeout | 1 minuto
 checkpoint_completion_target | 0.7
 checkpoint_segments | 40
 client_min_messages | Avviso
 cpu_index_tuple_cost | 0.001
 cpu_operator_cost | 0,0005
 cpu_tuple_cost | 0.003
 efficient_cache_size | 1530000kB
 hot_standby | su
 lc_collate | en_US.UTF-8
 lc_ctype | en_US.UTF-8
 ascolta_indirizzo | *
 log_checkpoints | su
 log_destination | syslog
 log_line_prefix | % u [GIALLO]
 log_min_duration_statement | 50ms
 log_min_messages | Avviso
 logging_collector | su
 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
 codifica_server | UTF8
 shared_buffers | 415MB
 ssl | su
 syslog_ident | resource29857_heroku_com
 TimeZone | UTC
 wal_buffers | 8MB
 wal_keep_segments | 127
 wal_level | hot_standby
 work_mem | 100MB
 (39 file)

MODIFICARE

Sembra che ORDER BYsia la parte 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. con 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

Un po 'non capisco ancora perché sia ​​più lento. Sembra che stia recuperando la stessa quantità di righe da Bitmap Heap Scan, ma impiega molto più tempo?


Se aumentare work_mem non fornisce abbastanza un aumento delle prestazioni, si prega di mostrare i risultati di EXPLAIN ANALYZE invece di EXPLAIN. Aiuta anche a mostrare i risultati dell'esecuzione della query in questa pagina: wiki.postgresql.org/wiki/Server_Configuration Anche una breve descrizione dell'hardware aiuta.
kgrittn,

Ecco un altro SPIEGAZIONE ANALISI: (vedi modifica nel mio post sopra)
xlash

Dovrei sottolineare che la configurazione di PostgreSQL che mostri non è probabile che sia vicina a quella ottimale. Questo è abbastanza fuori tema che probabilmente dovrebbe essere affrontato in una domanda separata, però. Ti consiglio di farlo.
kgrittn,

Se hai problemi a comprendere l'output di EXPLAIN ANALYZE, c'è una pagina Wiki che dovrebbe aiutarti: wiki.postgresql.org/wiki/Using_EXPLAIN Molte persone trovano utile la pagina spiegazione.depesz.com ; potresti voler dare una occhiata all'aiuto e provarlo.
kgrittn,

Risposte:


8

Quello che ancora non capisco, è perché questo è più lento.

Che l'ordinamento delle righe costerà qualcosa è ovvio. Ma perché così tanto?
Senza ORDER BY rank0...Postgres può solo

  • scegli le prime 5 righe che trova e smetti di recuperare le righe non appena ne ha 5.

    Scansione heap bitmap su entità ... righe = 5 ...

  • quindi calcola ts_rank()solo per 5 righe.
Nel secondo caso, Postgres deve

  • recupera tutte (1495 in base al tuo piano di query) idonee.

    Scansione heap bitmap su entità ... righe = 1495 ...

  • calcola ts_rank()per tutti loro.
  • ordinali tutti per trovare i primi 5 in base al valore calcolato.
Prova ORDER BY namesolo a vedere il costo del calcolo to_tsquery('english', 'hockey'::text))per le righe superflue e quanto resta per recuperare più righe e ordinare.


Il caching si frappone ... dà all'incirca una performance altrettanto brutta. 10 secondi 1500 file. Capisco la tua spiegazione. Ha senso. Ma mentre fai una ricerca di testo .... un modo per creare il mio indice per un ordinamento di qualità adeguato senza estrarre tutto?
xlash,

5

Una scansione bitmap funziona in questo modo: l'indice viene scansionato per trovare le posizioni delle tuple corrispondenti alle condizioni dell'indice. La bitmap potrebbe passare attraverso una combinazione logica con i risultati di altre scansioni bitmap, utilizzando la logica booleana sui bit. Quindi si accede alle pagine che contengono i dati in ordine numerico, per ridurre l'accesso al disco e, si spera, trasformare alcune letture casuali in letture sequenziali.

Una scansione dell'indice bitmap potrebbe dover diventare "lossy" per adattarsi alla memoria: riduce la sua precisione dal livello della tupla al livello della pagina. Se aumenti work_mem (almeno per questa query), potresti evitarlo. Non sarà in grado di saltare la scansione dell'heap bitmap sulla riga 4 o il filtro sulla riga 6, ma potrebbe essere in grado di saltare il controllo sulla riga 5.


1
Aumentato da 100 MB a 650 MB, non ha prodotto differenze di prestazioni. (work_mem)
xlash,

5

Dal momento che la domanda modificata sembra che l'obiettivo sia quello di scegliere alcune delle migliori partite, piuttosto che un elenco di tutto ciò che corrisponde, sto postando una risposta separata per questo, dal momento che è un problema piuttosto diverso.

Potresti prendere in considerazione un indice KNN - GiST (per K Neighbor più vicino - Albero di ricerca generalizzato), che può estrarre dall'indice in ordine di somiglianza - quindi non è necessario leggere casualmente tutte quelle tuple di heap e ordinare giù per trovare le migliori partite K.

Ad oggi non credo che nessuno abbia implementato il supporto KNN-GIST per le query tsearch (anche se mi è stato assicurato che può essere fatto, è solo una questione di qualcuno che si prende il tempo per farlo), ma forse il supporto per il trigramma (che è fatto) funzionerà per la tua applicazione. La differenza principale è che le ricerche del trigramma non usano dizionari per la derivazione e sinonimi come fa tsearch; troverai solo corrispondenze di parole esatte.

Per provare i trigrammi per il tuo esempio, probabilmente vuoi indicizzare "name" in questo modo:

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

Quindi puoi cercare in questo modo:

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

Nota gli operatori utilizzati e l' ORDER BYutilizzo dell'alias della colonna "similarità". Non mi allontanerei troppo da questo schema quando lo provavo. L'indice su tsvector non viene utilizzato per questa ricerca.

Escludendo i problemi con la configurazione corrente (che potrebbe facilmente portare l'intera VM in un paging senza speranza dal sovraccarico di memoria), probabilmente ti piaceranno molto le prestazioni di questo. Se ha il comportamento che desideri è ciò che non so.

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.