web-dev-qa-db-fra.com

Amélioration de la vitesse de requête: SELECT simple dans une grande table postgres

J'ai des problèmes de vitesse dans une requête SELECT sur une base de données Postgres.

J'ai une table avec deux colonnes entières comme clé: (int1, int2) Cette table a environ 70 millions de lignes.

Je dois faire deux types de requêtes SELECT simples dans cet environnement:

SELECT * FROM table WHERE int1=X;
SELECT * FROM table WHERE int2=X;

Ces deux sélections renvoient environ 10 000 lignes sur chacune de ces 70 millions. Pour que cela fonctionne aussi vite que possible, j'ai pensé à utiliser deux index HASH, un pour chaque colonne. Malheureusement, les résultats ne sont pas si bons:

                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on lec_sim  (cost=232.21..25054.38 rows=6565 width=36) (actual time=14.759..23339.545 rows=7871 loops=1)
   Recheck Cond: (lec2_id = 11782)
   ->  Bitmap Index Scan on lec_sim_lec2_hash_ind  (cost=0.00..230.56 rows=6565 width=0) (actual time=13.495..13.495 rows=7871 loops=1)
         Index Cond: (lec2_id = 11782)
 Total runtime: 23342.534 ms
(5 rows)

Ceci est un exemple EXPLAIN ANALYZE d'une de ces requêtes. Cela prend environ 23 secondes. Mes attentes sont d'obtenir cette information en moins d'une seconde.

Voici quelques paramètres de la configuration postgres db:

work_mem = 128MB
shared_buffers = 2GB
maintenance_work_mem = 512MB
fsync = off
synchronous_commit = off
effective_cache_size = 4GB

Toute aide, commentaire ou pensée serait vraiment appréciée.

Merci d'avance.

24
alexdemartos

Extraire mes commentaires dans une réponse: la recherche dans l'index a été très rapide - tout le temps a été consacré à l'extraction des lignes réelles. 23 secondes/7871 lignes = 2,9 millisecondes par ligne, ce qui est raisonnable pour récupérer des données dispersées dans le sous-système de disque. Les recherches sont lentes; vous pouvez a) adapter votre jeu de données à la mémoire vive, b) acheter des disques SSD ou c) organiser vos données à l'avance pour minimiser les recherches.

PostgreSQL 9.2 possède une fonctionnalité appelée analyses d’index uniquement qui lui permet de répondre (généralement) à des requêtes sans accéder à la table. Vous pouvez combiner cela avec la propriété btree index de maintien automatique de l'ordre pour accélérer cette requête. Vous mentionnez int1, int2 et deux flottants:

CREATE INDEX sometable_int1_floats_key ON sometable (int1, float1, float2);
CREATE INDEX sometable_int2_floats_key ON sometable (int2, float1, float2);

SELECT float1,float2 FROM sometable WHERE int1=<value>; -- uses int1 index
SELECT float1,float2 FROM sometable WHERE int2=<value>; -- uses int2 index

Notez également que cela n'efface pas comme par magie le disque recherché, il les déplace simplement du temps de la requête au temps de l'insertion. Cela vous coûte également de l'espace de stockage, car vous dupliquez les données. Pourtant, c’est probablement le compromis que vous souhaitez.

31
willglynn

Merci willglyn. Comme vous l'avez remarqué, le problème était de chercher dans la HD et de ne pas rechercher les index. Vous avez proposé de nombreuses solutions, telles que le chargement du jeu de données dans RAM ou l'achat d'un disque SSD HD. Mais en oubliant ces deux aspects, qui impliquent la gestion d'éléments en dehors de la base de données elle-même, vous avez proposé deux idées:

  1. Réorganisez les données pour réduire la recherche de données.
  2. Utiliser la fonctionnalité "analyses avec index uniquement" de PostgreSQL 9.2

Étant donné que je suis sous un serveur PostgreSQL 9.1, j'ai décidé de choisir l'option "1".

J'ai fait une copie de la table. Alors maintenant, j'ai la même table avec les mêmes données deux fois. J'ai créé un index pour chacun, le premier indexé par (int1) et le second par (int2). Ensuite, je les ai regroupés tous les deux (table CLUSTER utilisant IND_INX) par leurs index respectifs.

Je publie maintenant un EXPLAIN ANALYSE de la même requête, effectué dans l'une des tables en cluster suivantes:

 QUERY PLAN 
------------------------------------------------- -------------------------------------------------- ---------------------------------------------- 
 Index Analysez lec_sim_lec2id_ind avec lec_sim_lec2id (coût = 0.00..21626.82 rows = 6604 width = 36) (heure réelle = 0.051..1.500 rows = 8119 boucles = 1) 
 Index Cond: (lec2_id = 12300) Durée d'exécution totale: 
 .1,822 ms (3 lignes) 

Maintenant, la recherche est vraiment rapide. Je suis passé de 23 secondes à ~ 2 millisecondes, ce qui constitue une amélioration impressionnante. Je pense que ce problème est résolu pour moi, j'espère que cela pourra être utile également pour les autres personnes confrontées au même problème.

Merci beaucoup willglynn.

20
alexdemartos

Un cas de requêtes très lentes où des jointures simples à multiples (dans PG v9.1) ont été effectuées entre une table de 33 millions de lignes et une table enfant de 2,4 milliards de lignes. J'ai effectué un CLUSTER sur l'index de clé étrangère pour la table enfant, mais j'ai constaté que cela ne résolvait pas mon problème de dépassement du délai d'attente des requêtes, même pour les requêtes les plus simples. Exécuter ANALYZE n'a pas non plus résolu le problème.

Ce qui a fait toute la différence, c’est d’effectuer un VACUUM manuel sur la table parent et la table enfant. Alors même que la table mère achevait son processus VACUUM, je suis passé de 10 minutes de délai d'attente à des résultats en une seconde.

Ce que j’en déduis, c’est que les opérations régulières de VACUUM sont toujours critiques, même pour la v9.1. La raison pour laquelle j'ai fait cela, c'est que j'ai remarqué que l'autovacuum ne fonctionnait sur aucune des tables depuis au moins deux semaines et que de nombreux montées et sorties avaient eu lieu depuis. Il se peut que je doive améliorer le déclencheur Autovacuum pour régler ce problème à l'avenir, mais ce que je peux dire, c'est qu'un tableau de 640 Go comportant quelques milliards de lignes fonctionne correctement si tout est nettoyé. Je n'ai pas encore eu à partitionner la table pour obtenir de bonnes performances.

3
Robert Casey

Pour un seul support très simple et efficace, si vous avez un stockage à l'état solide rapide sur votre machine Postgres, essayez de régler:

random_page_cost=1.0

Dans votre dans votre postgresql.conf

La valeur par défaut est random_page_cost=4.0. Ceci est optimisé pour le stockage avec des temps de recherche élevés, tels que les anciens disques tournants. Cela modifie le calcul des coûts pour la recherche et s'appuie moins sur votre mémoire (qui pourrait éventuellement être échangé de toute façon) 

Ce paramètre à lui seul a amélioré ma requête de filtrage de 8 secondes à 2 secondes sur une longue table contenant quelques millions d'enregistrements. 

L'autre amélioration majeure provient de la création d'index avec toutes les colonnes booleen de ma table. Cela a réduit la requête de 2 secondes à environ 1 seconde. Vérifiez la réponse de @ willglynn pour cela. 

J'espère que cela t'aides!

0
Nick Woodhams