web-dev-qa-db-fra.com

L'augmentation de work_mem et shared_buffers sur Postgres 9.2 ralentit considérablement les requêtes

J'ai une instance PostgreSQL 9.2 exécutée sur RHEL 6.3, une machine à 8 cœurs avec 16 Go de RAM. Le serveur est dédié à cette base de données. Étant donné que le postgresql.conf par défaut est assez conservateur en ce qui concerne les paramètres de mémoire, j'ai pensé que ce serait une bonne idée d'autoriser Postgres à utiliser plus de mémoire. À ma grande surprise, suivre les conseils sur wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server a considérablement ralenti pratiquement toutes les requêtes que j'exécute, mais c'est évidemment plus visible sur les requêtes plus complexes.

J'ai également essayé d'exécuter pgtune qui a donné la recommandation suivante avec plus de paramètres ajustés, mais cela n'a rien changé. Il propose des shared_buffers de 1/4 de RAM taille qui semble en ligne avec les conseils ailleurs (et sur PG wiki en particulier).

default_statistics_target = 50
maintenance_work_mem = 960MB
constraint_exclusion = on
checkpoint_completion_target = 0.9
effective_cache_size = 11GB
work_mem = 96MB
wal_buffers = 8MB
checkpoint_segments = 16
shared_buffers = 3840MB
max_connections = 80

J'ai essayé de réindexer toute la base de données après avoir modifié les paramètres (en utilisant reindex database), Mais cela n'a pas aidé non plus. J'ai joué avec shared_buffers et work_mem. Les modifier progressivement par rapport aux valeurs par défaut très conservatrices (128 Ko/1 Mo) a progressivement diminué les performances.

J'ai exécuté EXPLAIN (ANALYZE,BUFFERS) sur quelques requêtes et le coupable semble être que Hash Join est beaucoup plus lent. Je ne comprends pas pourquoi.

Pour donner un exemple spécifique, j'ai la requête suivante. Il s'exécute en ~ 2100 ms sur la configuration par défaut et ~ 3300 ms sur la configuration avec des tailles de mémoire tampon accrues:

select count(*) from contest c
left outer join contestparticipant cp on c.id=cp.contestId
left outer join teammember tm on tm.contestparticipantid=cp.id
left outer join staffmember sm on cp.id=sm.contestparticipantid
left outer join person p on p.id=cp.personid
left outer join personinfo pi on pi.id=cp.personinfoid
where pi.lastname like '%b%' or pi.firstname like '%a%';

EXPLAIN (ANALYZE,BUFFERS) pour la requête ci-dessus:

La question est pourquoi j'observe une diminution des performances lorsque j'augmente la taille des tampons? La machine ne manque certainement pas de mémoire. L'allocation si la mémoire partagée dans le système d'exploitation est (shmmax et shmall) est définie sur des valeurs très grandes, cela ne devrait pas poser de problème. Je ne reçois aucune erreur dans le journal Postgres non plus. J'utilise autovacuum dans la configuration par défaut, mais je ne m'attends pas à ce que cela ait quelque chose à voir avec cela. Toutes les requêtes ont été exécutées sur la même machine à quelques secondes d'intervalle, juste avec une configuration modifiée (et PG redémarré).

Edit: Je viens de découvrir un fait particulièrement intéressant: lorsque je fais le même test sur mon iMac mi-2010 (OSX 10.7.5) également avec Postgres 9.2.1 et 16 Go de RAM, je ne subis pas le ralentissement. Plus précisément:

set work_mem='1MB';
select ...; // running time is ~1800 ms
set work_mem='96MB';
select ...' // running time is ~1500 ms

Lorsque je fais exactement la même requête (celle ci-dessus) avec exactement les mêmes données sur le serveur, j'obtiens 2100 ms avec work_mem = 1 Mo et 3200 ms avec 96 Mo.

Le Mac a un SSD, c'est donc plus rapide, mais il présente un comportement que j'attendrais.

Voir aussi discussion de suivi sur pgsql-performance .

44
Petr Praus

Tout d'abord, gardez à l'esprit que work_mem est par opération et qu'il peut donc devenir excessif assez rapidement. En général, si vous ne rencontrez pas de problèmes avec les tris qui sont lents, je laisserais work_mem seul jusqu'à ce que vous en ayez besoin.

En regardant vos plans de requête, une chose qui me frappe est que les hits du tampon sont très différents en regardant les deux plans, et que même les analyses séquentielles sont plus lentes. Je soupçonne que le problème a à voir avec la mise en cache en lecture anticipée et avec moins d'espace pour cela. Cela signifie que vous polarisez la mémoire pour la réutilisation des index et contre la lecture des tables sur le disque.


Ma compréhension est que PostgreSQL cherchera le cache d'une page avant de le lire à partir du disque car il ne sait pas vraiment si le cache du système d'exploitation contiendra cette page. Étant donné que les pages restent ensuite dans le cache et que ce cache est plus lent que le cache du système d'exploitation, cela modifie les types de requêtes qui sont rapides par rapport aux types qui sont lents. En fait, en lisant les plans, à part les problèmes de work_mem, il semble que toutes vos informations de requête proviennent du cache, mais il s'agit de savoir quel cache.

work_mem: la quantité de mémoire que nous pouvons allouer pour une opération de tri ou de jointure associée. C'est par opération, pas par instruction ou par back-end, donc une seule requête complexe peut utiliser plusieurs fois cette quantité de mémoire. Il n'est pas clair que vous atteignez cette limite, mais il convient de le noter et d'en être conscient. si vous augmentez cela trop loin, vous perdez de la mémoire qui pourrait être disponible pour le cache de lecture et les tampons partagés.

shared_buffers: quantité de mémoire à allouer à la file d'attente de pages PostgreSQL réelle. Maintenant, idéalement, l'ensemble intéressant de votre base de données restera en mémoire ici et dans les tampons de lecture. Cependant, cela garantit que les informations les plus fréquemment utilisées sur tous les backends sont mises en cache et non vidées sur le disque. Sous Linux, ce cache est beaucoup plus lent que le cache de disque du système d'exploitation, mais il garantit que le cache de disque du système d'exploitation ne fonctionne pas et est transparent pour PostgreSQL. C'est assez clairement où se situe votre problème.

Donc, ce qui se passe, c'est que lorsque nous avons une demande, nous vérifions d'abord les tampons partagés car PostgreSQL a une connaissance approfondie de ce cache et recherchons les pages. S'ils ne sont pas là, nous demandons au système d'exploitation de les ouvrir à partir du fichier et si le système d'exploitation a mis en cache le résultat, il renvoie la copie en cache (c'est plus rapide que les tampons partagés, mais Pg ne peut pas dire s'il est mis en cache ou activé. disque, et le disque est beaucoup plus lent, donc PostgreSQL ne prendra généralement pas cette chance). Gardez à l'esprit que cela affecte également l'accès aléatoire aux pages séquentielles. Ainsi, vous pouvez obtenir de meilleures performances avec des paramètres shared_buffers inférieurs.

Mon intuition est que vous obtenez probablement de meilleures performances, ou du moins plus cohérentes, dans des environnements à concurrence élevée avec des paramètres shared_buffer plus importants. Gardez également à l'esprit que PostgreSQL récupère cette mémoire et la conserve donc si vous avez d'autres choses en cours d'exécution sur le système, les tampons de lecture contiendront les fichiers lus par d'autres processus. C'est un sujet très vaste et complexe. De plus grands paramètres de mémoire tampon partagée offrent de meilleures garanties de performances, mais peuvent fournir moins de performances dans certains cas.

31
Chris Travers

Outre l'effet apparemment paradoxal que l'augmentation de work_mem diminue les performances ( @ Chris pourrait avoir une explication), vous pouvez améliorer votre fonction d'au moins deux façons.

  • Réécrivez deux faux LEFT JOIN est avec JOIN. Cela pourrait confondre le planificateur de requêtes et conduire à des plans inférieurs.
SELECT count(*) AS ct
FROM   contest            c
JOIN   contestparticipant cp ON cp.contestId = c.id
JOIN   personinfo         pi ON pi.id = cp.personinfoid
LEFT   JOIN teammember    tm ON tm.contestparticipantid = cp.id
LEFT   JOIN staffmember   sm ON sm.contestparticipantid = cp.id
LEFT   JOIN person        p  ON p.id = cp.personid
WHERE (pi.firstname LIKE '%a%'
OR     pi.lastname  LIKE '%b%')
  • En supposant que vos modèles de recherche réels soient plus sélectifs, utilisez des index de trigrammes sur pi.firstname et pi.lastname pour prendre en charge les recherches LIKE non ancrées. (Modèles plus courts comme '%a%' sont également pris en charge, mais un index ne sera probablement pas utile pour les prédicats non sélectifs.):
CREATE INDEX personinfo_firstname_gin_idx ON personinfo USING gin (firstname gin_trgm_ops);
CREATE INDEX personinfo_lastname_gin_idx  ON personinfo USING gin (lastname gin_trgm_ops);

Ou un index multicolonne:

CREATE INDEX personinfo_name_gin_idx ON personinfo USING gin (firstname gin_trgm_ops, lastname gin_trgm_ops);

Cela devrait rendre votre requête un peu plus rapide. Vous devez installer le module supplémentaire pg_trgm pour cela. Détails sous ces questions connexes:


De plus, avez-vous essayé de définir work_memlocalement - pour la transaction en cours uniquement ?

SET LOCAL work_mem = '96MB';

Cela empêche les transactions simultanées de consommer également plus de RAM, éventuellement de s'affamer.

12
Erwin Brandstetter