La performance est l'une des tâches les plus importantes et les plus complexes lors de la gestion d'une base de données. Il peut être affecté par la configuration, le matériel ou même la conception du système. Par défaut, PostgreSQL est configuré dans un souci de compatibilité et de stabilité, car les performances dépendent beaucoup du matériel et de notre système lui-même. Nous pouvons avoir un système avec beaucoup de données lues mais les informations ne changent pas fréquemment. Ou nous pouvons avoir un système qui écrit en continu. Pour cette raison, il est impossible de définir une configuration par défaut qui fonctionne pour tous les types de charges de travail.
Dans ce blog, nous verrons comment on procède pour analyser la charge de travail, ou les requêtes, en cours d'exécution. Nous passerons ensuite en revue quelques paramètres de configuration de base pour améliorer les performances de notre base de données PostgreSQL. Comme nous l'avons mentionné, nous ne verrons que certains des paramètres. La liste des paramètres PostgreSQL est longue, nous n'aborderons que certains des principaux. Cependant, on peut toujours consulter la documentation officielle pour se plonger dans les paramètres et configurations qui semblent les plus importants ou utiles dans notre environnement.
EXPLIQUER
L'une des premières étapes que nous pouvons suivre pour comprendre comment améliorer les performances de notre base de données consiste à analyser les requêtes effectuées.
PostgreSQL conçoit un plan de requête pour chaque requête qu'il reçoit. Pour voir ce plan, nous utiliserons EXPLAIN.
La structure d'un plan de requête est une arborescence de nœuds de plan. Les nœuds du niveau inférieur de l'arborescence sont des nœuds d'analyse. Ils renvoient des lignes brutes d'une table. Il existe différents types de nœuds d'analyse pour différentes méthodes d'accès à la table. La sortie EXPLAIN comporte une ligne pour chaque nœud de l'arborescence du plan.
world=# EXPLAIN SELECT * FROM city t1,country t2 WHERE id>100 AND t1.population>700000 AND t2.population<7000000;
QUERY PLAN
--------------------------------------------------------------------------
Nested Loop (cost=0.00..734.81 rows=50662 width=144)
-> Seq Scan on city t1 (cost=0.00..93.19 rows=347 width=31)
Filter: ((id > 100) AND (population > 700000))
-> Materialize (cost=0.00..8.72 rows=146 width=113)
-> Seq Scan on country t2 (cost=0.00..7.99 rows=146 width=113)
Filter: (population < 7000000)
(6 rows)
Cette commande montre comment les tables de notre requête seront analysées. Voyons à quoi correspondent ces valeurs que nous pouvons observer dans notre EXPLAIN.
- Le premier paramètre indique l'opération que le moteur effectue sur les données à cette étape.
- Coût de démarrage estimé. Il s'agit du temps passé avant que la phase de sortie puisse commencer.
- Coût total estimé. Ceci est indiqué en supposant que le nœud de plan est exécuté jusqu'à la fin. En pratique, le nœud parent d'un nœud peut s'arrêter avant de lire toutes les lignes disponibles.
- Estimation du nombre de lignes générées par ce nœud de plan. Encore une fois, le nœud est supposé être exécuté jusqu'à la fin.
- Largeur moyenne estimée des lignes générées par ce nœud de plan.
La partie la plus critique de l'affichage est le coût d'exécution estimé de l'instruction, qui est l'estimation du planificateur du temps qu'il faudra pour exécuter l'instruction. Lorsque nous comparons l'efficacité d'une requête par rapport à l'autre, nous comparerons en pratique les valeurs de coût de celles-ci.
Il est important de comprendre que le coût d'un nœud de niveau supérieur inclut le coût de tous ses nœuds enfants. Il est également important de réaliser que le coût ne reflète que les éléments qui intéressent le planificateur. En particulier, le coût ne prend pas en compte le temps passé à transmettre les lignes de résultat au client, qui pourrait être un facteur important dans le temps réel écoulé; mais le planificateur l'ignore car il ne peut pas le changer en modifiant le plan.
Les coûts sont mesurés en unités arbitraires déterminées par les paramètres de coût du planificateur. La pratique traditionnelle consiste à mesurer les coûts en unités d'extraction de pages de disque ; autrement dit, seq_page_cost est défini par convention sur 1,0 et les autres paramètres de coût sont définis par rapport à cela.
EXPLIQUER ANALYSER
Avec cette option, EXPLAIN exécute la requête, puis affiche le nombre réel de lignes et le temps d'exécution réel accumulé dans chaque nœud du plan, ainsi que les mêmes estimations qu'un EXPLAIN simple affiche.
Voyons un exemple d'utilisation de cet outil.
world=# EXPLAIN ANALYZE SELECT * FROM city t1,country t2 WHERE id>100 AND t1.population>700000 AND t2.population<7000000;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..734.81 rows=50662 width=144) (actual time=0.081..22.066 rows=51100 loops=1)
-> Seq Scan on city t1 (cost=0.00..93.19 rows=347 width=31) (actual time=0.069..0.618 rows=350 loops=1)
Filter: ((id > 100) AND (population > 700000))
Rows Removed by Filter: 3729
-> Materialize (cost=0.00..8.72 rows=146 width=113) (actual time=0.000..0.011 rows=146 loops=350)
-> Seq Scan on country t2 (cost=0.00..7.99 rows=146 width=113) (actual time=0.007..0.058 rows=146 loops=1)
Filter: (population < 7000000)
Rows Removed by Filter: 93
Planning time: 0.136 ms
Execution time: 24.627 ms
(10 rows)
Si nous ne trouvons pas la raison pour laquelle nos requêtes prennent plus de temps qu'elles ne le devraient, nous pouvons consulter ce blog pour plus d'informations.
VIDE
Le processus VACUUM est responsable de plusieurs tâches de maintenance au sein de la base de données, l'une d'entre elles récupérant le stockage occupé par les tuples morts. Dans le fonctionnement normal de PostgreSQL, les tuples qui sont supprimés ou rendus obsolètes par une mise à jour ne sont pas physiquement supprimés de leur table; ils restent présents jusqu'à ce qu'un VACUUM soit effectué. Par conséquent, il est nécessaire de faire le VACUUM périodiquement, en particulier dans les tables fréquemment mises à jour.
Si le VACUUM prend trop de temps ou de ressources, cela signifie que nous devons le faire plus fréquemment, afin que chaque opération ait moins à nettoyer.
Dans tous les cas, vous devrez peut-être désactiver le VACUUM, par exemple lors du chargement de données en grande quantité.
Le VACUUM récupère simplement de l'espace et le rend disponible pour une réutilisation. Cette forme de commande peut fonctionner en parallèle avec la lecture et l'écriture normales de la table, puisqu'un verrou exclusif n'est pas obtenu. Cependant, l'espace supplémentaire n'est pas restitué au système d'exploitation (dans la plupart des cas) ; il n'est disponible que pour être réutilisé dans le même tableau.
VACUUM FULL réécrit tout le contenu de la table dans un nouveau fichier disque sans espace supplémentaire, ce qui permet à l'espace inutilisé de retourner au système d'exploitation. Ce formulaire est beaucoup plus lent et nécessite un verrou exclusif sur chaque table lors du traitement.
VACUUM ANALYZE effectue un VACUUM puis une ANALYZE pour chaque table sélectionnée. C'est un moyen pratique de combiner des scripts de maintenance de routine.
ANALYZE collecte des statistiques sur le contenu des tables de la base de données et stocke les résultats dans pg_statistic. Par la suite, le planificateur de requêtes utilise ces statistiques pour aider à déterminer les plans d'exécution les plus efficaces pour les requêtes.
Téléchargez le livre blanc aujourd'hui PostgreSQL Management &Automation with ClusterControlDécouvrez ce que vous devez savoir pour déployer, surveiller, gérer et faire évoluer PostgreSQLTélécharger le livre blancParamètres de configuration
Pour modifier ces paramètres il faut éditer le fichier $PGDATA/postgresql.conf. Nous devons garder à l'esprit que certains d'entre eux nécessitent un redémarrage de notre base de données.
max_connexions
Détermine le nombre maximum de connexions simultanées à notre base de données. Certaines ressources mémoire peuvent être configurées par client. Par conséquent, le nombre maximal de clients peut suggérer la quantité maximale de mémoire utilisée.
superuser_reserved_connections
En cas d'atteinte de la limite de max_connection, ces connexions sont réservées au superutilisateur.
tampons_partagés
Définit la quantité de mémoire utilisée par le serveur de base de données pour les tampons de mémoire partagée. Si vous avez un serveur de base de données dédié avec 1 Go ou plus de RAM, une valeur initiale raisonnable pour shared_buffers est de 25 % de la mémoire de votre système. Des configurations plus importantes pour les shared_buffers nécessitent généralement une augmentation correspondante de max_wal_size, pour étendre le processus d'écriture de grandes quantités de données nouvelles ou modifiées sur une plus longue période de temps.
temp_buffers
Définit le nombre maximal de tampons temporaires utilisés pour chaque session. Ce sont des tampons de session locaux utilisés uniquement pour accéder aux tables temporaires. Une session affectera les tampons temporaires selon les besoins jusqu'à la limite donnée par temp_buffers.
work_mem
Spécifie la quantité de mémoire qui sera utilisée par les opérations internes de ORDER BY, DISTINCT, JOIN et les tables de hachage avant d'écrire dans les fichiers temporaires sur le disque. Lors de la configuration de cette valeur, nous devons tenir compte du fait que plusieurs sessions exécutent ces opérations en même temps et chaque opération sera autorisée à utiliser autant de mémoire que spécifié par cette valeur avant de commencer à écrire des données dans des fichiers temporaires.
Cette option s'appelait sort_mem dans les anciennes versions de PostgreSQL.
maintenance_work_mem
Spécifie la quantité maximale de mémoire utilisée par les opérations de maintenance, telles que VACUUM, CREATE INDEX et ALTER TABLE ADD FOREIGN KEY. Étant donné qu'une seule de ces opérations peut être exécutée en même temps par une session, et qu'une installation n'en a généralement pas beaucoup en cours d'exécution simultanément, elle peut être plus grande que work_mem. Des configurations plus importantes peuvent améliorer les performances de VACUUM et des restaurations de bases de données.
Lorsque l'autovacuum est exécuté, cette mémoire peut être affectée au nombre de fois où le paramètre autovacuum_max_workers est configuré, nous devons donc en tenir compte, ou sinon, configurer le paramètre autovacuum_work_mem pour gérer cela séparément.
fsync
Si fsync est activé, PostgreSQL essaiera de s'assurer que les mises à jour sont écrites physiquement sur le disque. Cela garantit que le cluster de bases de données peut être restauré dans un état cohérent après une panne du système d'exploitation ou du matériel.
Bien que la désactivation de fsync améliore généralement les performances, elle peut entraîner une perte de données en cas de panne de courant ou de plantage du système. Par conséquent, il est conseillé de désactiver fsync uniquement si vous pouvez facilement recréer l'intégralité de votre base de données à partir de données externes.
checkpoint_segments (PostgreSQL <9.5)
Nombre maximum de segments de fichier d'enregistrement entre les points de contrôle WAL automatiques (chaque segment fait normalement 16 mégaoctets). L'augmentation de ce paramètre peut augmenter le temps nécessaire pour récupérer les défauts. Dans un système avec beaucoup de trafic, cela peut affecter les performances s'il est défini sur une valeur très faible. Il est recommandé d'augmenter la valeur de checkpoint_segments sur les systèmes avec de nombreuses modifications de données.
De plus, une bonne pratique consiste à enregistrer les fichiers WAL sur un disque autre que PGDATA. Ceci est utile à la fois pour équilibrer l'écriture et pour la sécurité en cas de panne matérielle.
Depuis PostgreSQL 9.5, la variable de configuration "checkpoint_segments" a été supprimée et remplacée par "max_wal_size" et "min_wal_size"
max_wal_size (PostgreSQL>=9.5)
Taille maximale que le WAL est autorisé à croître entre les points de contrôle. La taille de WAL peut dépasser max_wal_size dans des circonstances particulières. L'augmentation de ce paramètre peut augmenter le temps nécessaire pour récupérer les défauts.
min_wal_size (PostgreSQL>=9.5)
Lorsque le fichier WAL est maintenu en dessous de cette valeur, il est recyclé pour une utilisation future à un point de contrôle, au lieu d'être supprimé. Cela peut être utilisé pour s'assurer que suffisamment d'espace WAL est réservé pour gérer les pics d'utilisation de WAL, par exemple lors de l'exécution de gros travaux par lots.
wal_sync_method
Méthode utilisée pour forcer les mises à jour WAL sur le disque. Si fsync est désactivé, ce paramètre n'a aucun effet.
wal_buffers
La quantité de mémoire partagée utilisée pour les données WAL qui n'ont pas encore été écrites sur le disque. Le paramètre par défaut est d'environ 3 % de shared_buffers, pas moins de 64 Ko ou plus que la taille d'un segment WAL (généralement 16 Mo). Définir cette valeur sur au moins quelques Mo peut améliorer les performances d'écriture sur un serveur avec de nombreuses transactions simultanées.
effective_cache_size
Cette valeur est utilisée par le planificateur de requêtes pour prendre en compte les plans qui peuvent ou non tenir en mémoire. Ceci est pris en compte dans les estimations de coût d'utilisation d'un indice; une valeur élevée rend plus probable l'utilisation de parcours d'index et une valeur faible rend plus probable l'utilisation de parcours séquentiels. Une valeur raisonnable serait 50 % de la RAM.
default_statistics_target
PostgreSQL collecte les statistiques de chacune des tables de sa base de données pour décider comment les requêtes seront exécutées sur celles-ci. Par défaut, il ne collecte pas trop d'informations, et si vous n'obtenez pas de bons plans d'exécution, vous devez augmenter cette valeur, puis relancer ANALYZE dans la base de données (ou attendre l'AUTOVACUUM).
synchronous_commit
Spécifie si la validation de la transaction attendra que les enregistrements WAL soient écrits sur le disque avant que la commande ne renvoie une indication "succès" au client. Les valeurs possibles sont :"on", "remote_apply", "remote_write", "local" et "off". Le paramètre par défaut est "activé". Lorsqu'il est désactivé, il peut y avoir un délai entre le moment où le client revient et le moment où la transaction est garantie d'être sécurisée contre un verrou de serveur. Contrairement à fsync, la désactivation de ce paramètre ne crée aucun risque d'incohérence de la base de données :un plantage du système d'exploitation ou de la base de données peut entraîner la perte de certaines transactions récentes prétendument validées, mais l'état de la base de données sera exactement le même que si ces transactions avait été annulé proprement. Par conséquent, la désactivation de synchronous_commit peut être une alternative utile lorsque les performances sont plus importantes que la certitude exacte quant à la durabilité d'une transaction.
Journalisation
Il existe plusieurs types de données à enregistrer qui peuvent être utiles ou non. Voyons-en quelques-uns :
- log_min_error_statement :définit le niveau de journalisation minimum.
- log_min_duration_statement :utilisé pour enregistrer les requêtes lentes dans le système.
- log_line_prefix :colle les informations au début de chaque ligne de journal.
- log_statement :vous pouvez choisir entre NONE, DDL, MOD, ALL. L'utilisation de "tout" peut entraîner des problèmes de performances.
Conception
Dans de nombreux cas, la conception de notre base de données peut affecter les performances. Nous devons être prudents dans notre conception, normaliser notre schéma et éviter les données redondantes. Dans de nombreux cas, il est pratique d'avoir plusieurs petites tables au lieu d'une immense table. Mais comme nous l'avons déjà dit, tout dépend de notre système et il n'y a pas une seule solution possible.
Nous devons également utiliser les index de manière responsable. Nous ne devons pas créer d'index pour chaque champ ou combinaison de champs, car, bien que nous n'ayons pas à parcourir toute la table, nous utilisons de l'espace disque et ajoutons une surcharge aux opérations d'écriture.
Un autre outil très utile est la gestion du pool de connexion. Si nous avons un système avec beaucoup de charge, nous pouvons l'utiliser pour éviter de saturer les connexions dans la base de données et pour pouvoir les réutiliser.
Matériel
Comme nous l'avons mentionné au début de ce blog, le matériel est l'un des facteurs importants qui affectent directement les performances de notre base de données. Voyons quelques points à garder à l'esprit.
- Mémoire :plus nous avons de RAM, plus nous pouvons gérer de données de mémoire, et cela signifie de meilleures performances. La vitesse d'écriture et de lecture sur disque est beaucoup plus lente qu'en mémoire, par conséquent, plus nous pouvons avoir d'informations en mémoire, meilleures seront les performances.
- CPU :Peut-être que cela n'a pas beaucoup de sens, mais plus nous avons de CPU, mieux c'est. En tout cas ce n'est pas le plus important en termes de matériel, mais si nous pouvons avoir un bon CPU, notre capacité de traitement s'améliorera et cela impacte directement notre base de données.
- Disque dur :Nous avons plusieurs types de disques que nous pouvons utiliser, SCSI, SATA, SAS, IDE. Nous avons également des disques SSD. Il faut comparer qualité/prix, qu'il faut utiliser pour comparer sa rapidité. Mais le type de disque n'est pas la seule chose à considérer, il faut aussi voir comment les configurer. Si nous voulons de bonnes performances, nous pouvons utiliser RAID10, en gardant les WAL sur un autre disque en dehors du RAID. Il n'est pas recommandé d'utiliser RAID5 car les performances de ce type de RAID pour les bases de données ne sont pas bonnes.
Conclusion
Après avoir pris en compte les points évoqués dans ce blog, nous pouvons effectuer un benchmark pour vérifier le comportement de la base de données.
Il est également important de faire surveiller notre base de données pour déterminer si nous sommes confrontés à un problème de performances et pour pouvoir le résoudre dans les plus brefs délais. Pour cette tâche, il existe plusieurs outils tels que Nagios, ClusterControl ou Zabbix, entre autres, qui nous permettent non seulement de surveiller, mais avec certains d'entre eux, nous permettent de prendre des mesures proactives avant que le problème ne se produise. Avec ClusterControl, en plus de la surveillance, de l'administration et de plusieurs autres utilitaires, nous pouvons recevoir des recommandations sur les actions que nous pouvons entreprendre lors de la réception d'alertes de performances. Cela nous permet d'avoir une idée de la façon de résoudre les problèmes potentiels.
Ce blog n'est pas destiné à être un guide exhaustif sur la façon d'améliorer les performances de la base de données. Espérons que cela donne une image plus claire de ce que les choses peuvent devenir importantes et de certains des paramètres de base qui peuvent être configurés. N'hésitez pas à nous faire savoir si nous en avons oublié d'importants.