web-dev-qa-db-fra.com

PostgreSQL NOT IN array slow query

J'ai une grande table avec des millions de lignes. Chaque ligne a un champ de tableau tags. J'ai également l'index GIN approprié sur tags.

Le comptage des lignes qui ont une balise est rapide (~ 7s):

SELECT COUNT(*) FROM "subscriptions" WHERE (tags @> ARRAY['t1']::varchar[]);

Cependant, compter les lignes qui n'ont pas de balise est extrêmement lent (~ 70s):

SELECT COUNT(*) FROM "subscriptions" WHERE NOT (tags @> ARRAY['t1']::varchar[]);

J'ai également essayé d'autres variantes, mais avec les mêmes résultats (~ 70s):

SELECT COUNT(*) FROM "subscriptions" WHERE NOT ('t1' = ANY (tags));

Comment puis-je accélérer l’opération "hors baie"?

3
collimarco

J'ai résolu merci à Jeff Janes sur la liste de diffusion pgsql-performance:

L'index GIN n'a pas été utilisé par PostgreSQL pour l'opération "NOT". La création d'un index Btree sur l'ensemble du tableau a résolu le problème, permettant une analyse d'index uniquement. Maintenant, la requête ne prend que quelques millisecondes au lieu de quelques minutes.

3
collimarco

Si 't1' est une balise rare, compter les lignes qui n'ont pas une balise permet de compter la plupart de vos "millions de lignes". Et même si "t1" est très courant, compter plus de quelques pour cent des lignes de l'index n'est pas une amélioration par rapport à un balayage séquentiel. Quoi qu'il en soit, cela ne sera jamais très rapide. Les index ne vont pas aider.

Si vous devez effectuer plusieurs comptages à l'exclusion des balises rares - et que le nombre total de lignes ne change pas entre-temps (ou que le changement minimal n'a pas d'importance), une optimisation possible serait d'obtenir le nombre total de lignes une fois (lent) et soustraire le (petit) nombre de lignes avec la balise (rapide avec index correspondant) ...

Selon les exigences exactes et votre cas d'utilisation complet, il peut y avoir d'autres raccourcis. Voir:

En bout de ligne, les index ne peuvent généralement aider qu'à identifier un pourcentage relativement faible de lignes de tableau. BTW, IN, = ANY() et les opérateurs de confinement @> sont des outils connexes, mais avec de subtiles différences. Les index GIN ne prennent généralement en charge que les opérateurs de tableau appropriés. Voir:

Vous pouvez tirer quelque chose de l'utilisation de tableaux entiers en combinaison avec des opérateurs et un index basé sur une classe d'opérateur fournie par le module intarray supplémentaire. Hautement optimisé, mais il ne peut pas défier les principes ci-dessus.

Vous pouvez ensuite également combiner any mixture of tags that the row must have or must not have comme vous l'avez commenté dans un query_int expression .

1
Erwin Brandstetter

Vous avez déjà eu une bonne réponse, alors voici un peu plus à classer sous matière à réflexion. Tout d'abord, votre question m'a rappelé une technique à consonance intéressante:

https://heap.io/blog/engineering/running-10-million-postgresql-indexes-in-production

Je serais intéressé par les commentaires de ceux qui ont essayé une telle stratégie.

Comme une autre pensée, une autre option est de maintenir vos propres tables de fréquences pour les balises et leur occurrence. Cela peut vous donner des informations pour guider votre propre générateur de code. L'idée ici est que le planificateur/optimiseur de requête générique ne peut jamais en savoir autant sur votre spécifique comme vous le faites. Avec des nombres de fréquences, même des nombres approximatifs raisonnablement bons, vous pouvez créer différentes requêtes à soumettre à Postgres pour différents cas.

Concrétiser cette idée du nombre de fréquences

J'élabore un peu ici car ma réponse abrégée d'origine n'était pas claire. L'idée ici est que vous pouvez maintenir une table de décomptes de fréquence, comme tag_count avec des balises uniques et un nombre. Ces petites données vous permettent de tester la façon dont les balises courantes dans une requête sont avant de générer la requête réelle pour Postgres. Ce plan "simple" dépend de plusieurs choses, dont un certain nombre peut ne pas être vrai dans votre cas:

  • Vous disposez d'un code qui compose les requêtes qui peuvent être modifiées pour effectuer cette étape de prétraitement afin de déterminer la meilleure façon de composer la requête.

  • Vous pouvez trouver des moyens d'utiliser le nombre de fréquences pour aider le planificateur à faire un meilleur travail.

  • Il existe un moyen pour vous d'exécuter le code de mise à jour du nombre de fréquences.

  • Il existe un moyen de maintenir les comptes avec une fidélité adéquate et sans embourber le système.

Ce dernier point est un sujet énorme, évidemment. Le moyen le plus simple (conceptuellement) est un déclencheur pour ajouter/mod/supprimer qui trouve les anciennes et les nouvelles balises et ajuste les décomptes en conséquence. Pas la solution la plus performante et un goulot d'étranglement potentiel. Il existe de très nombreux modèles alternatifs. (Un déclencheur au niveau de l'instruction avec une table de file d'attente de post-réconciliation serait une conception alternative qui n'est pas un goulot d'étranglement.) Honnêtement, je ne connais pas encore les stratégies les plus performantes pour les mises à jour incrémentielles dans Postgres. J'ai esquissé ~ 10 stratégies pour moi-même il y a quelques mois, mais je n'ai pas recommencé à tester et comparer des solutions. D'autres personnes sur ce forum utilisent Postgres depuis des lustres et sont super intelligentes et utiles. Donc, si ce type de solution est ce que vous recherchez, cela vaut la peine de le demander à nouveau.

1
Morris de Oryx