Optimiser ORDER BY dans une requête de recherche en texte intégral

8

J'ai une grande table entitiesavec environ 15 millions d'enregistrements. Je veux trouver les 5 premières lignes correspondant à «hockey» dans leur name.

J'ai un index de texte intégral sur name, qui est utilisé:gin_ix_entity_full_text_search_name

Requete:

 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

Durée 25,623 ms

Expliquez le plan
1 Limite (coût = 12666.89..12666.89 lignes = 5 largeur = 3116)
2 -> Trier (coût = 12666.89..12670.18 lignes = 6571 largeur = 3116)
3 Clé de tri: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'hockey' '' :: tsquery))
4 -> Bitmap Heap Scan sur les entités (coût = 124.06..12645.06 lignes = 6571 largeur = 3116)
5 Vérifiez à nouveau Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)
6 Filtre: (PAS placé)
7 -> Scan d'index bitmap sur gin_ix_entity_full_text_search_name (coût = 0,00..123,74 lignes = 6625 largeur = 0)
8 Index Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)

Je ne comprends pas pourquoi il vérifie la condition d'index deux fois. (Recherchez les étapes 4 et 7 du plan). Est-ce à cause de ma condition booléenne ( not place)? Si oui, dois-je l'ajouter à mon index pour obtenir une requête très rapide? Ou la condition de tri la ralentit-elle?

EXPLAIN ANALYZE production:

  Limite (coût = 4447.28..4447.29 lignes = 5 largeur = 3116) (temps réel = 18509.274..18509.282 lignes = 5 boucles = 1)
  -> Trier (coût = 4447.28..4448.41 lignes = 2248 largeur = 3116) (temps réel = 18509.271..18509.273 lignes = 5 boucles = 1)
         Clé de tri: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'test' '' :: tsquery))
         Méthode de tri: top-N heapsort Mémoire: 19 Ko
     -> Bitmap Heap Scan sur les entités (coût = 43,31..4439,82 lignes = 2248 largeur = 3116) (temps réel = 119,003..18491,408 lignes = 2533 boucles = 1)
           Vérifiez à nouveau Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery)
           Filtre: (PAS placer)
           -> Bitmap Index Scan sur gin_ix_entity_full_text_search_name (coût = 0,00..43,20 lignes = 2266 largeur = 0) (temps réel = 74,093..74,093 lignes = 2593 boucles = 1)
                 Index Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery)
 Durée totale: 18509,381 ms

Voici mes paramètres DB. Il est hébergé par Heroku, sur les services Amazon. Ils le décrivent comme ayant 1,7 Go de RAM, 1 unité de traitement et une base de données de 1 To maximum.

nom | paramètre actuel
------------------------------ + ------------------- -------------------------------------------------- ------------------------------------
 version | PostgreSQL 9.0.7 sur i486-pc-linux-gnu, compilé par GCC gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3, 32 bits
 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 | sur
 archive_timeout | 1 minute
 checkpoint_completion_target | 0,7
 checkpoint_segments | 40
 client_min_messages | remarquer
 cpu_index_tuple_cost | 0,001
 cpu_operator_cost | 0,0005
 cpu_tuple_cost | 0,003
 effective_cache_size | 1530000kB
 hot_standby | sur
 lc_collate | en_US.UTF-8
 lc_ctype | en_US.UTF-8
 listen_addresses | *
 log_checkpoints | sur
 log_destination | syslog
 log_line_prefix | % u [JAUNE]
 log_min_duration_statement | 50ms
 log_min_messages | remarquer
 logging_collector | sur
 maintenance_work_mem | 64 Mo
 max_connections | 500
 max_prepared_transactions | 500
 max_stack_depth | 2 Mo
 max_standby_archive_delay | -1
 max_standby_streaming_delay | -1
 max_wal_senders | dix
 port | 
 random_page_cost | 2
 encodage_serveur | UTF8
 shared_buffers | 415MB
 ssl | sur
 syslog_ident | resource29857_heroku_com
 TimeZone | UTC
 wal_buffers | 8 Mo
 wal_keep_segments | 127
 wal_level | pose chaude
 work_mem | 100 Mo
 (39 rangées)

ÉDITER

On dirait que ORDER BYc'est la partie lente:

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

Contre. avec 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

Mais je ne comprends toujours pas pourquoi c'est plus lent. On dirait qu'il récupère la même quantité de lignes à partir de Bitmap Heap Scan, mais cela prend beaucoup plus de temps?

xlash
la source
Si l'augmentation de work_mem n'apporte pas suffisamment d'amélioration des performances, veuillez afficher les résultats d'EXPLAIN ANALYZE au lieu de simplement EXPLAIN. Il permet également d'afficher les résultats de l'exécution de la requête sur cette page: wiki.postgresql.org/wiki/Server_Configuration Une brève description du matériel est également utile.
kgrittn
Voici un autre EXPLAIN ANALYZE: (voir la modification dans mon article ci-dessus)
xlash
Je dois souligner que la configuration PostgreSQL que vous montrez n'est probablement pas proche de l'optimale. C'est suffisamment loin du sujet pour qu'il soit probablement abordé dans une question distincte. Je vous recommande de le faire.
kgrittn
Si vous rencontrez des problèmes pour comprendre votre sortie EXPLAIN ANALYZE, il existe une page Wiki qui devrait vous aider: wiki.postgresql.org/wiki/Using_EXPLAIN Beaucoup de gens trouvent la page d' expliquer.depesz.com utile; vous voudrez peut-être fouiller dans l'aide et l'essayer.
kgrittn

Réponses:

8

Ce que je ne comprends toujours pas, c'est pourquoi c'est plus lent.

Le tri des lignes coûtera quelque chose est évident. Mais pourquoi autant?
Sans ORDER BY rank0...Postgres, on peut juste

  • choisissez les 5 premières lignes qu'il trouve et arrêtez de récupérer les lignes dès qu'il en a 5.

    Bitmap Heap Scan sur les entités ... lignes = 5 ...

  • puis calculez ts_rank()pour seulement 5 lignes.
Dans le second cas, Postgres doit

  • récupérer toutes les lignes (1495 selon votre plan de requête) qualifiées.

    Bitmap Heap Scan sur les entités ... lignes = 1495 ...

  • calculer ts_rank()pour chacun d'eux.
  • triez-les tous pour trouver les 5 premiers en fonction de la valeur calculée.
Essayez ORDER BY namesimplement de voir le coût de calcul to_tsquery('english', 'hockey'::text))pour les lignes superflues et combien il reste pour récupérer plus de lignes et trier.

Erwin Brandstetter
la source
La mise en cache intervient ... elle donne à peu près une performance aussi mauvaise. 10secs 1500 lignes. Je comprends votre explication. Ca a du sens. Mais tout en faisant une recherche de texte .... une façon de construire mon index pour un tri de bonne qualité sans tout extraire?
xlash
5

Une analyse bitmap fonctionne comme ceci: L'index est analysé pour trouver les emplacements des tuples correspondant aux conditions d'index. Le bitmap peut passer par une combinaison logique avec les résultats d'autres analyses bitmap, en utilisant une logique booléenne sur les bits. Ensuite, les pages contenant les données sont accessibles dans l'ordre des numéros de page, pour réduire l'accès au disque et, espérons-le, transformer certaines lectures aléatoires en lectures séquentielles.

Un scan d'index bitmap peut avoir besoin de devenir "avec perte" pour tenir en mémoire - il réduit sa précision du niveau de tuple au niveau de la page. Si vous augmentez work_mem (au moins pour cette seule requête), vous pouvez éviter cela. Il ne pourra pas ignorer l'analyse de tas de bitmap sur la ligne 4 ou le filtre sur la ligne 6, mais il pourra peut-être ignorer la nouvelle vérification sur la ligne 5.

kgrittn
la source
1
Augmentation de 100 Mo à 650 Mo, aucune différence de performances. (work_mem)
xlash
5

Étant donné que la question modifiée donne l'impression que le but est de choisir quelques-unes des meilleures correspondances, plutôt qu'une liste de tout ce qui correspond, je poste une réponse distincte pour cela, car c'est un problème assez différent.

Vous voudrez peut-être envisager un index KNN - GiST (pour K Nearest Neighbour - Generalized Search Tree), qui peut être extrait directement de l'index par ordre de similitude - vous n'avez donc pas besoin de lire au hasard tous ces tuples de tas et de les trier les descendre pour trouver les K meilleurs matchs.

À ce jour, je ne pense pas que quiconque ait implémenté le support KNN-GIST pour les requêtes tsearch (même si j'ai été assuré que cela peut être fait, c'est juste une question de quelqu'un qui prend le temps de le faire), mais peut-être le support du trigramme (qui est fait) fonctionnera pour votre application. La principale différence est que les recherches de trigrammes n'utilisent pas de dictionnaires pour la racine et les synonymes comme le fait tsearch; vous ne trouverez que des correspondances exactes de mots.

Pour essayer des trigrammes pour votre exemple, vous voudrez probablement indexer "nom" comme ceci:

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

Ensuite, vous pouvez rechercher comme ceci:

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

Notez les opérateurs utilisés et l' ORDER BYutilisation de l'alias de la colonne "similarité". Je ne m'éloignerais pas trop de ce schéma en l'essayant. L'index sur le tsvector n'est pas utilisé pour cette recherche.

À moins de problèmes avec votre configuration actuelle (qui pourraient facilement jeter toute votre machine virtuelle dans une pagination désespérée à partir d'une surcharge de mémoire), vous aimerez probablement vraiment les performances de cela. Je ne sais pas s'il a le comportement que vous voulez.

kgrittn
la source