Optimieren von ORDER BY in einer Volltextsuchabfrage

8

Ich habe einen großen Tisch entitiesmit ~ 15 Millionen Datensätzen. Ich möchte die Top 5 Reihen finden, die zu 'Hockey' passen name.

Ich habe einen Volltextindex name, der verwendet wird:gin_ix_entity_full_text_search_name

Abfrage:

 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

Dauer 25.623 ms

Plan erklären
1 Limit (Kosten = 12666.89..12666.89 Zeilen = 5 Breite = 3116)
2 -> Sortieren (Kosten = 12666.89..12670.18 Zeilen = 6571 Breite = 3116)
3 Sortierschlüssel: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'hockey' '' :: tsquery))
4 -> Bitmap-Heap-Scan für Entitäten (Kosten = 124.06..12645.06 Zeilen = 6571 Breite = 3116)
5 Überprüfen Sie erneut Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)
6 Filter: (NICHT platzieren)
7 -> Bitmap-Index-Scan für gin_ix_entity_full_text_search_name (Kosten = 0,00..123,74 Zeilen = 6625 Breite = 0)
8 Index Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)

Ich verstehe nicht, warum es die Indexbedingung zweimal überprüft. (Abfrageplan Schritt 4 und 7). Liegt es an meiner booleschen Bedingung ( not place)? Wenn ja, sollte ich es meinem Index hinzufügen, um eine sehr schnelle Abfrage zu erhalten? Oder macht es die Sortierbedingung langsam?

EXPLAIN ANALYZE Ausgabe:

  Limit (Kosten = 4447,28..4447,29 Zeilen = 5 Breite = 3116) (tatsächliche Zeit = 18509,274..18509,282 Zeilen = 5 Schleifen = 1)
  -> Sortieren (Kosten = 4447,28..4448,41 Zeilen = 2248 Breite = 3116) (tatsächliche Zeit = 18509,271..18509,273 Zeilen = 5 Schleifen = 1)
         Sortierschlüssel: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'test' '' :: tsquery))
         Sortiermethode: Top-N-Heapsort Speicher: 19 KB
     -> Bitmap-Heap-Scan für Entitäten (Kosten = 43.31..4439.82 Zeilen = 2248 Breite = 3116) (tatsächliche Zeit = 119.003..18491.408 Zeilen = 2533 Schleifen = 1)
           Überprüfen Sie erneut Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery)
           Filter: (NICHT platzieren)
           -> Bitmap-Index-Scan für gin_ix_entity_full_text_search_name (Kosten = 0,00..43,20 Zeilen = 2266 Breite = 0) (tatsächliche Zeit = 74,093..74,093 Zeilen = 2593 Schleifen = 1)
                 Index Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery)
 Gesamtlaufzeit: 18509.381 ms

Hier sind meine DB-Parameter. Es wird von Heroku auf Amazon-Diensten gehostet. Sie beschreiben es mit 1,7 GB RAM, 1 Prozessoreinheit und einer DB von maximal 1 TB.

Name | Aktuelle Einstellung
------------------------------ + ------------------- -------------------------------------------------- ------------------------------------
 version | PostgreSQL 9.0.7 auf i486-pc-linux-gnu, kompiliert von GCC gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3, 32-Bit
 archive_command | 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
 archive_mode | auf
 archive_timeout | 1 Minute
 checkpoint_completion_target | 0,7
 checkpoint_segments | 40
 client_min_messages | beachten
 cpu_index_tuple_cost | 0,001
 cpu_operator_cost | 0,0005
 cpu_tuple_cost | 0,003
 effektive_cache_size | 1530000kB
 hot_standby | auf
 lc_collate | en_US.UTF-8
 lc_ctype | en_US.UTF-8
 listen_addresses | * *
 log_checkpoints | auf
 log_destination | Syslog
 log_line_prefix | % u [GELB]
 log_min_duration_statement | 50ms
 log_min_messages | beachten
 logging_collector | auf
 wartung_arbeit_mem | 64 MB
 max_connections | 500
 max_prepared_transactions | 500
 max_stack_depth | 2 MB
 max_standby_archive_delay | -1
 max_standby_streaming_delay | -1
 max_wal_senders | 10
 port | 
 random_page_cost | 2
 server_encoding | UTF8
 shared_buffers | 415 MB
 ssl | auf
 syslog_ident | resource29857_heroku_com
 Zeitzone | koordinierte Weltzeit
 wal_buffers | 8 MB
 wal_keep_segments | 127
 wal_level | Hot-Standby
 work_mem | 100 MB
 (39 Zeilen)

BEARBEITEN

Sieht aus wie ORDER BYist der langsame Teil:

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. mit 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

Ich verstehe immer noch nicht, warum das langsamer ist. Sieht so aus, als würde es die gleiche Anzahl von Zeilen aus Bitmap Heap Scan abrufen, aber es dauert so viel länger?

xlash
quelle
Wenn das Erhöhen von work_mem nicht genug Leistungssteigerung bietet, zeigen Sie bitte die Ergebnisse von EXPLAIN ANALYZE anstatt nur EXPLAIN. Es ist auch hilfreich , die Ergebnisse der Ausführung der Abfrage auf dieser Seite anzuzeigen : wiki.postgresql.org/wiki/Server_Configuration Eine kurze Beschreibung der Hardware hilft ebenfalls.
kgrittn
Hier ist eine weitere EXPLAIN ANALYZE: (siehe Bearbeiten in meinem obigen Beitrag)
xlash
Ich möchte darauf hinweisen, dass die von Ihnen gezeigte PostgreSQL-Konfiguration wahrscheinlich nicht annähernd optimal ist. Das ist weit genug vom Thema entfernt, dass es wahrscheinlich in einer separaten Frage behandelt werden sollte. Ich empfehle Ihnen dies zu tun.
kgrittn
Wenn Sie Probleme haben, Ihre EXPLAIN ANALYZE-Ausgabe zu verstehen, gibt es eine Wiki-Seite, die helfen sollte: wiki.postgresql.org/wiki/Using_EXPLAIN Viele Leute finden die EXPLAIN.depesz.com- Seite hilfreich. Vielleicht möchten Sie sich in der Hilfe umsehen und sie ausprobieren.
kgrittn

Antworten:

8

Was ich immer noch nicht verstehe, ist, warum dies langsamer ist.

Dass das Sortieren der Zeilen etwas kostet , liegt auf der Hand. Aber warum so viel?
Ohne ORDER BY rank0...Postgres kann man einfach

  • Wählen Sie die ersten 5 gefundenen Zeilen aus und hören Sie auf, Zeilen abzurufen, sobald 5 vorhanden sind.

    Bitmap-Heap-Scan für Entitäten ... Zeilen = 5 ...

  • Berechnen Sie dannts_rank() für nur 5 Zeilen.
Im zweiten Fall muss Postgres

  • Rufen Sie alle qualifizierten Zeilen (1495 gemäß Ihrem Abfrageplan) ab.

    Bitmap-Heap-Scan für Entitäten ... Zeilen = 1495 ...

  • Berechnen Sie ts_rank()für alle.
  • Sortieren Sie alle, um die ersten 5 nach dem berechneten Wert zu finden.
Versuchen Sie ORDER BY namenur, die Rechenkosten to_tsquery('english', 'hockey'::text))für die überflüssigen Zeilen zu ermitteln und festzustellen, wie viel für das Abrufen weiterer Zeilen und das Sortieren noch übrig ist.

Erwin Brandstetter
quelle
Caching kommt im Weg ... es gibt ungefähr eine Leistung als schlecht. 10 Sekunden 1500 Zeilen. Ich verstehe deine Erklärung. Es ergibt Sinn. Aber während einer Textsuche ... gibt es eine Möglichkeit, meinen Index für eine ordnungsgemäße Sortierung zu erstellen, ohne alles zu extrahieren?
xlash
5

Ein Bitmap-Scan funktioniert folgendermaßen: Der Index wird gescannt, um die Positionen der Tupel zu finden, die den Indexbedingungen entsprechen. Die Bitmap kann eine logische Kombination mit den Ergebnissen anderer Bitmap-Scans durchlaufen, wobei eine boolesche Logik für die Bits verwendet wird. Dann wird auf die Seiten mit den Daten in der Reihenfolge der Seitenzahlen zugegriffen, um den Festplattenzugriff zu verringern und hoffentlich einige zufällige Lesevorgänge in sequentielle Lesevorgänge umzuwandeln.

Ein Bitmap-Index-Scan muss möglicherweise "verlustbehaftet" werden, um in den Speicher zu passen. Dadurch wird die Genauigkeit von der Tupelebene auf die Seitenebene reduziert. Wenn Sie work_mem erhöhen (zumindest für diese eine Abfrage), können Sie dies vermeiden. Es ist nicht möglich, den Bitmap-Heap-Scan in Zeile 4 oder den Filter in Zeile 6 zu überspringen, aber möglicherweise kann die erneute Überprüfung in Zeile 5 übersprungen werden.

kgrittn
quelle
1
Erhöht von 100 MB auf 650 MB, ergab keinen Leistungsunterschied. (work_mem)
xlash
5

Da die bearbeitete Frage so aussieht, als ob das Ziel darin besteht, einige der besten Übereinstimmungen auszuwählen, anstatt eine Liste aller Übereinstimmungen, veröffentliche ich eine separate Antwort darauf, da es sich um ein etwas anderes Problem handelt.

Vielleicht möchten Sie ein prüfen , KNN - GiST (für K Nearest Neighbor - Generalized Search Tree) Index, der direkt aus dem Index in der Reihenfolge der Ähnlichkeit ziehen kann - so dass Sie nicht zufällig alle jene Haufen Tupel zu lesen brauchen und sortieren sie unten, um die K besten Übereinstimmungen zu finden.

Bisher glaube ich nicht, dass irgendjemand die KNN-GIST-Unterstützung für Suchanfragen implementiert hat (obwohl mir versichert wurde, dass dies möglich ist, ist es nur eine Frage von jemandem, der sich die Zeit dafür nimmt), aber vielleicht die Trigram-Unterstützung (die ist erledigt) wird für Ihre Anwendung funktionieren. Der Hauptunterschied besteht darin, dass bei der Suche nach Trigrammen keine Wörterbücher für Stemming und Synonyme verwendet werden, wie dies bei der Suche der Fall ist. Sie finden nur genaue Wortübereinstimmungen.

Um Trigramme für Ihr Beispiel auszuprobieren, möchten Sie wahrscheinlich "Name" wie folgt indizieren:

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

Dann können Sie so suchen:

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

Beachten Sie die verwendeten Operatoren und den ORDER BYAlias ​​der Spalte "Ähnlichkeit". Ich würde mich beim Ausprobieren nicht zu weit von diesem Muster entfernen. Der Index auf dem tsvector wird für diese Suche nicht verwendet.

Abgesehen von Problemen mit Ihrer aktuellen Konfiguration (die Ihre gesamte VM aufgrund von Speicherüberlastung leicht in hoffnungsloses Paging versetzen könnten), wird Ihnen die Leistung wahrscheinlich sehr gut gefallen. Ob es das gewünschte Verhalten hat, weiß ich nicht.

kgrittn
quelle