J'ai un tableau qui contient des données extraites de documents texte. Les données sont stockées dans une colonne appelée "CONTENT"
pour laquelle j'ai créé cet index à l'aide de GIN:
CREATE INDEX "File_contentIndex"
ON "File"
USING gin
(setweight(to_tsvector('english'::regconfig
, COALESCE("CONTENT", ''::character varying)::text), 'C'::"char"));
J'utilise la requête suivante pour effectuer une recherche en texte intégral sur la table:
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 table File contient 250 000 lignes et chaque "CONTENT"
entrée se compose d'un mot aléatoire et d'une chaîne de texte identique pour toutes les lignes.
Maintenant, lorsque je recherche un mot aléatoire (1 hit dans tout le tableau), la requête s'exécute très rapidement (<100 ms). Cependant, lorsque je recherche un mot qui est présent dans toutes les lignes, la requête s'exécute extrêmement lentement (10 minutes ou plus).
EXPLAIN ANALYZE
montre que pour la recherche en une touche, un scan d'index bitmap suivi d'un scan de tas bitmap est effectué. Pour la recherche lente, une analyse Seq est effectuée à la place, ce qui prend si longtemps.
Certes, il n'est pas réaliste d'avoir les mêmes données dans toutes les lignes. Mais comme je ne peux pas contrôler les documents texte téléchargés par les utilisateurs, ni les recherches qu'ils effectuent, il est possible qu'un scénario similaire se produise (recherche sur des termes avec une occurrence très élevée dans la base de données). Comment puis-je augmenter les performances de ma requête de recherche pour un tel scénario?
Exécution de PostgreSQL 9.3.4
Plans de requête de EXPLAIN ANALYZE
:
Recherche rapide (1 hit dans 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"
Recherche lente (250k hits dans 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"
ORDER BY "RANK" DESC
. J'enquêtepg_trgm
avec l' index GiST et les opérateurs similarité / distance , comme alternative. Considérez: dba.stackexchange.com/questions/56224/… . Peut même produire de "meilleurs" résultats (en plus d'être plus rapide).explain (analyze, buffers)
, de préférence avec track_io_timing défini surON
? Il n'y a aucun moyen que cela prenne 520 secondes pour seq analyser cette table, sauf si vous l'avez stockée sur un RAID de disquettes. Quelque chose y est définitivement pathologique. En outre, quel est votre réglagerandom_page_cost
et les autres paramètres de coût?Réponses:
Cas d'utilisation douteux
Une chaîne de texte identique pour toutes les lignes n'est que du fret mort. Retirez-le et concaténez-le dans une vue si vous avez besoin de le montrer.
De toute évidence, vous en êtes conscient:
Mettez à niveau votre version Postgres
Alors que vous êtes toujours sur Postgres 9.3, vous devriez au moins mettre à niveau vers la dernière version ponctuelle (actuellement 9.3.9). La recommandation officielle du projet:
Mieux encore, passez à la version 9.4 qui a reçu des améliorations majeures pour les index GIN .
Problème majeur 1: Estimation des coûts
Le coût de certaines fonctions de recherche de texte a été sérieusement sous-estimé jusqu'à la version 9.4 incluse. Ce coût est augmenté du facteur 100 dans la prochaine version 9.5, comme @jjanes le décrit dans sa récente réponse:
Voici le fil respectif où cela a été discuté et le message de validation de Tom Lane.
Comme vous pouvez le voir dans le message de validation,
to_tsvector()
figure parmi ces fonctions. Vous pouvez appliquer la modification immédiatement (en tant que superutilisateur):ce qui devrait beaucoup plus probable que votre index fonctionnel est utilisé.
Problème majeur 2: KNN
Le problème principal est que Postgres doit calculer un classement avec
ts_rank()
260k lignes (rows=261011
) avant de pouvoir passer commande et sélectionner le top 5. Cela va coûter cher , même après avoir résolu d'autres problèmes comme discuté. C'est un problème K-le plus proche voisin (KNN) par nature et il existe des solutions pour les cas connexes. Mais je ne peux pas penser à une solution générale pour votre cas, car le calcul du classement lui-même dépend de l'entrée de l'utilisateur. J'essaierais d'éliminer le plus grand nombre de matchs de bas niveau tôt afin que le calcul complet ne soit effectué que pour quelques bons candidats.Une façon dont je peux penser est de combiner votre recherche de texte intégral avec la recherche de similitude de trigramme - qui offre une implémentation fonctionnelle pour le problème KNN. De cette façon, vous pouvez présélectionner les «meilleures» correspondances avec le
LIKE
prédicat en tant que candidats (dans une sous-requête avecLIMIT 50
par exemple), puis sélectionner les 5 premières lignes en fonction de votre classement dans la requête principale.Ou appliquez les deux prédicats dans la même requête et choisissez les correspondances les plus proches en fonction de la similitude des trigrammes (ce qui produirait des résultats différents) comme dans cette réponse connexe:
J'ai fait quelques recherches supplémentaires et vous n'êtes pas le premier à rencontrer ce problème. Related posts sur pgsql-general:
Des travaux sont en cours pour éventuellement mettre en place un
tsvector <-> tsquery
opérateur.Oleg Bartunov et Alexander Korotkov ont même présenté un prototype fonctionnel (utilisé
><
comme opérateur au lieu de l'<->
époque), mais son intégration dans Postgres est très complexe, toute l'infrastructure des index GIN doit être retravaillée (la plupart se fait maintenant).Problème majeur 3: poids et indice
Et j'ai identifié un facteur supplémentaire s'ajoutant à la lenteur de la requête. Par documentation:
Accentuation mienne. Dès que le poids est impliqué, chaque ligne doit être récupérée à partir du tas (pas seulement un contrôle de visibilité bon marché) et les valeurs longues doivent être grillées, ce qui augmente le coût. Mais il semble y avoir une solution à cela:
Définition d'index
En examinant à nouveau votre index, il ne semble pas logique de commencer. Vous attribuez un poids à une seule colonne, ce qui n'a aucun sens tant que vous ne concaténez pas d'autres colonnes avec un poids différent .
COALESCE()
n'a également aucun sens tant que vous ne concaténez pas plus de colonnes.Simplifiez votre index:
Et votre requête:
Toujours cher pour un terme de recherche qui correspond à chaque ligne, mais probablement beaucoup moins.
À part
Tous ces problèmes combinés, le coût insensé de 520 secondes pour votre deuxième requête commence à faire sens. Mais il peut encore y avoir plus de problèmes. Avez-vous configuré votre serveur?
Tous les conseils habituels pour l'optimisation des performances s'appliquent.
Cela vous simplifie la vie si vous ne travaillez pas avec des identificateurs de casse CaMeL entre guillemets doubles:
la source
USING gin (to_tsvector('english', "CONTENT")
J'ai eu un problème similaire. J'ai pris soin de cela en précalculant le ts_rank de chaque terme de requête de texte populaire par rapport à un champ: table tuple et en le stockant dans une table de recherche. Cela m'a fait gagner beaucoup de temps (facteur 40X) lors de la recherche de mots populaires dans le corpus lourd de texte.
Requête: recherchez ce tableau et obtenez les identifiants des documents triés par leur rang respectif. sinon, faites-le à l'ancienne.
la source