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

39

J'ai une instance de PostgreSQL 9.2 s'exécutant 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 fichier postgresql.conf par défaut est plutôt conservateur en ce qui concerne les paramètres de mémoire, j'ai pensé que ce serait une bonne idée de permettre à Postgres d'utiliser davantage 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 je lance, mais il est évidemment plus visible pour les requêtes plus complexes.

J'ai aussi essayé de lancer pgtune, ce qui a donné la recommandation suivante avec plus de paramètres ajustés, mais cela n'a rien changé. Il suggère des shared_buffers de 1/4 de la taille de la RAM, ce qui semble correspondre aux conseils donnés 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 l'ensemble de la base de données après avoir modifié les paramètres (utilisation reindex database), mais cela n'a pas aidé non plus. J'ai joué avec shared_buffers et work_mem. En les modifiant progressivement à partir des valeurs par défaut très conservatrices (128 Ko / 1 Mo), les performances ont progressivement diminué.

J'ai couru EXPLAIN (ANALYZE,BUFFERS)sur quelques requêtes et le coupable semble être que Hash Join est nettement plus lent. Ce n'est pas clair pour moi pourquoi.

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

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 qui se pose est de savoir pourquoi la performance diminue lorsque j'augmente la taille de la mémoire tampon. La machine ne manque définitivement pas de mémoire. L'allocation si la mémoire partagée dans le système d'exploitation est ( shmmaxet shmall) définie sur de très grandes valeurs, 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 pense pas que cela y soit pour quelque chose. Toutes les requêtes ont été exécutées sur le même ordinateur à quelques secondes d'intervalle, avec une configuration modifiée (et un redémarrage de la PG).

Edit: Je viens de découvrir un fait particulièrement intéressant: lorsque j’effectue le même test sur mon iMac de mi-2010 (OSX 10.7.5) également avec Postgres 9.2.1 et 16 Go de RAM, je n’éprouve 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, je reçois 2100 ms avec work_mem = 1Mo et 3200 ms avec 96 MB.

Le Mac est équipé de disques SSD, donc il est compréhensible de le faire plus rapidement, mais il présente un comportement auquel je m'attendrais.

Voir également la discussion de suivi sur pgsql-performance .

Petr Praus
la source
1
Il semble que dans le cas le plus lent, chaque étape est toujours plus lente. Les autres paramètres sont-ils restés les mêmes?
dezso
1
Cela vaut probablement la peine de poser la question à un forum plus spécialisé plutôt qu’à un forum générique. Dans ce cas, je suggère la liste de diffusion pgsql-general archives.postgresql.org/pgsql-general
Colin 't Hart
1
Oh, et faites votre rapport et répondez s'il vous plaît à votre propre question si vous trouvez la réponse! (Ceci est permis, même encouragé).
Colin 't Hart
1
Je me demande à quel point Postgres est similaire à Oracle à cet égard: je me souviens d'un cours de Jonathan Lewis (gourou d'Oracle) dans lequel il démontrait qu'allouer plus de mémoire à des tâches les ralentissait parfois. J'oublie les détails, mais c'est parce qu'Oracle fait des tris partiels, puis les écrit dans la mémoire de stockage temporaire, puis les combine plus tard. D'une manière ou d'une autre, plus de mémoire a ralenti ce processus.
Colin 't Hart
2
La question est maintenant affichée sur pgsql-performance: archives.postgresql.org/pgsql-performance/2012-11/msg00004.php
Petr Praus Du

Réponses:

28

Tout d'abord, gardez à l'esprit que work_mem est une opération et qu'il peut donc devenir excessif assez rapidement. En général, si vous ne rencontrez pas de problèmes de ralentissement, 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 de la mémoire tampon sont très différents si vous regardez 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 à lecture anticipée et d'avoir moins d'espace pour cela. Cela signifie que vous polarisez la mémoire pour réutiliser les index et contre les tables de lecture sur disque.


D'après ce que j'ai compris, PostgreSQL se penchera sur le cache pour 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. Comme les pages restent alors dans le cache et que ce dernier 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, mis à part les problèmes de work_mem, il semble que toutes les informations de votre requête proviennent du cache, mais la question est de savoir quel cache.

work_mem : combien de mémoire nous pouvons allouer pour un tri ou une opération de jointure associée. Il s’agit d’une opération à la fois, et non d’une instruction ou d’un back-end. Une requête complexe unique peut donc 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 prendre conscience. Si vous l'augmentez trop, 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. Maintenant, idéalement, l'ensemble intéressant de votre base de données restera dans la mémoire mise en cache ici et dans les mémoires tampons de lecture. Cependant, cela permet de s'assurer que les informations les plus fréquemment utilisées dans tous les backends sont mises en cache et ne sont pas vidées sur le disque. Sous Linux, ce cache est nettement plus lent que le cache de disque du système d'exploitation, mais il offre des garanties que le cache de disque du système d'exploitation n'est pas transparent et qu'il est transparent pour PostgreSQL. C'est assez clairement où est votre problème.

Donc, ce qui se passe, c'est que lorsque nous avons une requête, 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 présents, nous demandons au système d'exploitation de les ouvrir à partir du fichier. Si le système d'exploitation a mis le résultat en cache, il renvoie la copie mise en cache (cette procédure est plus rapide que les tampons partagés, mais Pg ne peut pas dire s'il est mis en cache ou non). disque, et le disque est beaucoup plus lent, donc PostgreSQL ne prend généralement pas cette chance). N'oubliez pas que cela affecte également l'accès aux pages aléatoire et séquentiel. Vous pouvez donc obtenir de meilleures performances avec des paramètres de shared_buffers plus bas.

Mon sens est que vous obtenez probablement de meilleures performances, ou du moins une plus grande cohérence, dans les environnements à haute concurrence avec des paramètres de shared_buffer plus importants. N'oubliez pas non plus que PostgreSQL ™ récupère cette mémoire et la conserve. Par conséquent, si d'autres éléments sont en cours d'exécution sur le système, les mémoires tampon de lecture contiendront les fichiers lus par d'autres processus. C'est un sujet très vaste et complexe. Des paramètres de mémoire tampon partagée plus grands offrent de meilleures garanties de performance, mais peuvent être moins performants dans certains cas.

Chris Travers
la source
10

Outre l'effet apparemment paradoxal que l'augmentation work_memdes performances diminue ( @Chris pourrait avoir une explication), vous pouvez améliorer votre fonction d'au moins deux manières.

  • Réécrivez deux faux LEFT JOINavec JOIN. Cela pourrait confondre le planificateur de requêtes et mener à 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 les index trigram sur pi.firstnameet pi.lastnamepour prendre en charge les LIKErecherches non ancrées . (Des modèles plus courts, comme ceux '%a%'pris en charge, 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);

Devrait rendre votre requête un peu plus rapide. Pour cela, vous devez installer le module supplémentaire pg_trgm . Détails sous ces questions connexes:


En outre, avez-vous essayé de définir work_mem localement - pour la transaction en cours uniquement ?

SET LOCAL work_mem = '96MB';

Cela empêche les transactions simultanées de consommer davantage de RAM, voire de s’affamer mutuellement.

Erwin Brandstetter
la source
3
Je souhaite appuyer la suggestion d'Erwin concernant les problèmes de travail locaux. Comme work_mem modifie les types de requêtes les plus rapides, vous devrez peut-être le changer pour certaines requêtes. Par exemple, les niveaux work_mem faibles sont préférables pour les requêtes qui trient / joignent un petit nombre d'enregistrements de manière complexe (c.-à-d. Beaucoup de jointures), tandis que les niveaux élevés work_mem sont préférables pour les requêtes ayant quelques tris mais qui trient ou joignent simultanément un grand nombre de lignes .
Chris Travers
J'ai amélioré la requête entre-temps (la question date d'octobre dernier) mais merci :) Cette question concerne davantage l'effet inattendu que la requête particulière. La requête sert principalement à démontrer l'effet. Merci pour le conseil sur l'index, je vais essayer ça!
Petr Praus