Options kernel sur Linux pour PostgreSQL¶
On suppose ici un serveur Linux dédié à PostgreSQL. Ce qui suit ne convient pas forcément en cas de cohabitation avec d’autres programmes, notamment avec des outils comme Java, python… qui réservent beaucoup de mémoire sans la consommer réellement.
Mise en place¶
Les valeurs en cours peuvent être consultées sous /proc/sys/.
Par exemple, cat /proc/sys/vm/swappiness
renvoie la valeur de vm.swappiness
.
En général, la valeur active peut être modifiée ainsi (en tant que root) :
Au redémarrage, cette configuration est perdue. Pour être conservées après un redémarrage, les valeurs sont à placer dans /etc/sysctl.conf
ou /etc/sysctl.d/99-postgresql.conf
. Certaines distributions peuvent déjà y placer un fichier de configuration, dont le contenu est souvent commenté.
Si l’on modifie un tel fichier, recharger la configuration avec :
ou, pour recharger tous les fichiers de configuration :
CPU¶
Chapitre détaillé dans le module J1 de notre formation PERF1
Options NUMA¶
Pour voir la topologie d’un serveur :
Les optimisations pour les architectures multi-sockets sont généralement contre-productives pour une base de données mais pas nécessairement pour d’autres cas d’utilisation. Dans certains cas extrêmes, nous avons vu des opérations s’exécuter sept fois plus vite après avoir désactivé les optimisations NUMA. Les problèmes rapportés dans le cadre d’un SGBD sont:
- des ralentissements dus à des déplacements de processus ou de mémoire entre nœuds NUMA ;
- une consommation CPU excessive sans raison apparente (trop de processus ancrés sur un CPU ou déplacements trop fréquents entre nœuds) ;
- la répartition contre productive des Huge Pages entre les nœuds NUMA ;
- une gestion indépendante de caches disques par nœud NUMA ;
- une gestion indépendante du swap par nœud NUMA.
Première méthode pour désactiver complètement NUMA :
- activation de “memory interleaving” dans le bios du serveur (afin que les CPU ne priorise pas la mémoire locale)
numa=off
dans les paramètres du kernel linux (par exemple sur RedHat : https://access.redhat.com/solutions/23216 )
Deuxième méthode :
- pour que la mémoire soit allouée équitablement sur tous les nœuds, exécuter (avant le lancement de PostgreSQL) :
- dans les paramètres kernel :
# ne pas déplacer les processus ou mémoire
kernel.numa_balancing=0
# ne pas gérer (gestion du cache et du swap) la mémoire par zone
vm.zone_reclaim_mode=0
vm.zone_reclaim_mode
est désactivé par défaut sur les noyaux et distributions récentes comme le précise la documentation de Linux : « zone_reclaim_mode is disabled by default. For file servers or workloads that benefit from having their data cached, zone_reclaim_mode should be left disabled as the caching effect is likely to be more important than data locality. ». Attention, ce n’est pas le cas de toutes les versions du noyau Linux ou des distributions Linux ! Veillez donc à vérifier ce paramètre, voir à le positionner malgré tout.
Références :
- https://www.enterprisedb.com/blog/linux-disables-vmzonereclaimmode-default
- https://www.kernel.org/doc/Documentation/sysctl/vm.txt
Enfin, augmenter de 0,5 à 5 ms le temps avant qu’un processus ne soit migré (kernels < 5.13) :
# kernels < 5.13
kernel.sched_migration_cost_ns=5000000
# kernels 5.13 et suivants
# à programmer au boot
echo 5000000 > /sys/kernel/debug/sched/migration_cost_ns
Autres¶
Désactiver le groupement des sessions par TTY (tout PostgreSQL tourne dans le même TTY) :
Voir https://man7.org/linux/man-pages/man7/sched.7.html.
Note
L’utilisation de cgroups autre que le cgroup CPU racine a précédence sur l’autogrouping, ce qui implique que pour les serveurs utilisant systemd
, cette option n’a aucun effet.
Mémoire¶
SWAP¶
Article complet : https://kb.dalibo.com/swap.
Sur un serveur moderne, plus de 2 Go de swap ne devrait servir à rien. C’est même dangereux sur un serveur de base de données qui ne doit absolument pas swapper sous peine d’effondrement des performances. En effet, les noyaux Linux anciens (avant la version 4.0) ont tendance à libérer préventivement de la mémoire en la descendant en cache. En revanche, il est utile d’en conserver un peu pour swapper des processus inactifs, ou le contenu de systèmes de fichiers tmpfs
(classiquement, /var/run
et les journaux de systemd-journald
selon la configuration de celui-ci).
Avec le paramètre suivant, on privilégie la récupération des pages mémoires utilisées pour le cache du système de fichiers (quitte à écrire des pages dirty), par rapport à la mise en swap des pages correspondant aux allocations mémoire des applications, ce qui peut être avantageux si le stockage est basé sur des disques rotatifs :
Overcommit¶
Article dédié : https://kb.dalibo.com/overcommit_memory.
Référence : https://www.postgresql.org/docs/current/static/kernel-resources.html#LINUX-MEMORY-OVERCOMMIT.
Linux prévoit que certaines applications réservent beaucoup plus de mémoire qu’elles n’en utiliseront (JVM…), et donc permet une certaine surallocation. Si cela arrive avec PostgreSQL, il y a de fort risque de swap, à éviter à tout prix.
Pour garantir aux applications que la mémoire réservée sera réellement disponible lors de l’allocation, modifier :
De plus, il faut toujours garder un peu de cache disque pour l’OS (15 à 20 % de la RAM). On limitera donc la proportion de mémoire réservable avec vm.overcommit_ratio
. On l’ajuste pour que CommitLimit
, qui est la mémoire maximale réservable (incluant la mémoire partagée et notamment shared buffers, la mémoire des backends, mémoire des outils système…), reste inférieure à la RAM physique.
Sans huge pages, on calcule ainsi :
soit :
La valeur suivante convient en général pour les machines possédant peu de swap par rapport à la RAM :
Par exemple, sur un serveur dédié de 32 Go de RAM et 2 Go de swap, on aura donc 2+32 * 80% = 27,6 Go réservables, et il restera environ 4 Go de cache pour l’OS. Sur une machine avec 4 Go de RAM et 2 Go de swap, il ne faudrait pas dépasser 30% (4 * 30% = 3,2 Go) pour que l’OS garde 0,8 Go de cache.
Alternativement, on peut modifier vm.overcommit_kbytes
pour indiquer une valeur fixe en kilo-octets. Par contre, il ne faudra pas oublier de modifier cette valeur en cas d’ajout de RAM.
Si le CommitLimit
est atteint, les réservations mémoire seront refusées et des connexions seront refusées, mais le serveur n’utilisera pas le swap et ne sera pas tué par l’OOM killer (voir https://kb.dalibo.com/overcommit_memory#manifestation_du_depassement_de_capacite).
L’utilisation des huge pages complique encore le calcul ; voir plus bas.
Shmmax et Shmall¶
Note
Depuis PostgreSQL 9.3, ces deux paramètres sont inutiles.
Référence : https://www.postgresql.org/docs/current/static/kernel-resources.html#SYSVIPC. (en 9.2 : https://www.postgresql.org/docs/9.2/static/kernel-resources.html)
SHMMAX¶
Jusqu’à PostgreSQL 9.2 inclus, kernel.shmmax
limitait la taille de la mémoire partagée IPC utilisée, et le paramétrage par défaut de Linux se limitait souvent à 32 Mo. Il était donc nécessaire de définir un kernel.shmmax
un peu supérieur aux shared_buffers
.
Depuis la 9.3, PostgreSQL n’utilise plus que quelques ko de mémoire IPC et n’a donc plus besoin de ce paramètre.
SHMALL¶
kernel.shmall
devait aussi être modifiée si l’on voulait 8 Go ou plus de shared buffers. La valeur est soit en octets soit en nombre de pages disques. La valeur par défaut était souvent 2097152, pour une page disque de 4 ko (taille à vérifier avec getconf PAGE_SIZE
), d’où une valeur un peu inférieure à 8 Go.
Sémaphores : SEMMNI, SMMNS…¶
Ces paramètres n’ont pas à être modifiés pour des installations « classiques » (une ou quelques instances avec des max_connections
raisonnables). Mais cela peut être nécessaire pour de nombreuses instances sur un même serveur, sous peine de refus de démarrage de PostgreSQL :
FATAL: could not create semaphores: No space left on device
HINT: This error does *not* mean that you have run out
of disk space. It occurs when either the system limit
for the maximum number of semaphore sets (SEMMNI), or the
system wide maximum number of semaphores (SEMMNS), would
be exceeded. You need to raise the respective kernel
parameter. Alternatively, reduce PostgreSQL's consumption
of semaphores by reducing its max_connections parameter.
Selon la documentation RH, les valeurs de (respectivement) SEMMSL, SEMMNS, SEMOPM, SEMMNI s’obtiennent avec (ici le défaut RH7) :
ou :
La documentation de PostgreSQL fournit les règles de calcul, par exemple :
SEMMNI = at least CEIL((max_connections + autovacuum_max_workers + max_worker_processes + 5) / 16)
plus room for other applications
Avec les valeurs par défaut de PostgreSQL et 30 instances, il faudra au moins :
Pour modifier les valeurs :
Debian semble poser des valeurs par défaut bien plus élevées (Debian GNU/Linux 9.8 (stretch)):
$ cat /proc/sys/kernel/sem
32000 1024000000 500 32000
$ ipcs -ls
------ Limites des sémaphores --------
nombre maximal de tableaux = 32000
nombre maximal de sémaphores par tableau = 32000
nombre maximal de sémaphores système = 1024000000
nombre maximal d'opérations par appel semop = 500
semaphore max value = 32767
Huge Pages¶
Article détaillé : https://kb.dalibo.com/huge_pages.
Référence :
- https://www.postgresql.org/docs/current/static/kernel-resources.html#LINUX-HUGE-PAGES ;
- https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt.
Les huges pages sont des pages mémoire de 2 Mo (voire plus). Leur utilisation améliore la consommation mémoire par rapport aux pages classiques de 4 ko. PostgreSQL (>=9.4) cherchera à utiliser les huge pages par défaut (paramètre huge_pages=try
).
On évitera les huge pages statiques pour éviter des problèmes en cas d’incohérences avec le shared buffers :
et on préférera les huge pages dynamiques ; pour créer n
pages (chacune de 2 Mo par défaut) :
Pour calculer n
, on peut se référer à la taille mémoire du postmaster (VmPeak
dans /proc/<PID>/status
) divisé par la taille des huge pages avec ~10% de supplément.
Attention à ne pas utiliser les transparent huge pages (THB), contre-productives sur une base de données :
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
Ces deux lignes devront être exécutées au démarrage du serveur, par exemple dans /etc/rc.local
, ou dans une ligne @reboot
du crontab, ou encore dans la configuration de grub
:
L’état des huge pages est visible dans :
cat /proc/meminfo | grep -i huge
AnonHugePages: 1605632 kB (transparent huge pages)
ShmemHugePages: 0 kB
HugePages_Total: 4103 (pool de huge pages total)
HugePages_Free: 2269 (huge pages non allouées)
HugePages_Rsvd: 2269 (huge pages réservées, non allouées)
HugePages_Surp: 4103 (huge pages dynamiques, au-delà de /proc/sys/vm/nr_hugepages,
dans la limite du total fixé par /proc/sys/vm/nr_overcommit_hugepages)
Hugepagesize: 2048 kB (taille de chaque huge page)
Systemd et utilisateur non standard¶
Dans le cas où l’instance ne tourne pas sous un utilisateur système (d’ID inférieur à 1000 généralement), il faut adapter un paramètre Systemd pour éviter des erreurs de mémoire partagée : voir https://www.postgresql.org/docs/14/kernel-resources.html#SYSTEMD-REMOVEIPC.
(Ceci ne concerne pas l’énorme majorité des installations, qui utilisent par défaut un utilisateur postgres créé par les scripts des paquets d’installation.)
Réseau¶
TCP Keepalive¶
Note
Préférer en général le paramétrage dans PostgreSQL.
Référence : (https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt)[https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt].
Au-delà de cette durée, Linux va lancer des sondes keepalive sur une connexion apparemment inutilisée, et la couper en cas d’absence de réponse. En cas de liaison correcte, la liaison continue de fonctionner normalement, il n’y a pas d’effet de bord.
Cette valeur est souvent positionnée à 7200 secondes (soit 2 heures). Il est possible de réduire cette valeur à 3600, voire moins, mais attention cela concerne tous les outils (rsync, ssh…) : faites attention à ne pas provoquer des coupures intempestives.
En général, on préférera modifier les paramètres équivalents dans le fichier postgresql.conf (uniquement pour les sessions PostgreSQL cette fois). On peut le descendre à 300 secondes (soit 5 minutes), par exemple pour éviter qu’une connexion coupée (mauvaise liaison, firewall trop agressif qui coupe les liaisons longues peu actives…) empêche de tuer une grosse requête : ainsi le serveur verra plus vite que la liaison au client est coupée.
Sockets en TIME WAIT¶
Attention
Exceptionnellement utile sur des réseaux instables avec beaucoup de connexions
PostgreSQL peut parfois laisser un nombre important de socket TCP ouverts et inutilisés (état TIME WAIT). Cet état se justifie lorsque les connexions TCP se font entre des serveurs éloignés ou dans des environnements à très forte charge. Cependant lorsque le serveur PostgreSQL est couplé avec un serveur middleware ou un pooler de connexions au sein d’un réseau local (LAN), conserver les sockets inactifs trop longtemps n’est pas crucial et l’on peut se permettre de les recycler plus vite pour éviter de tomber à court de ports :
Notons que cette préconisation peut aussi concerner d’autres applications, notamment Zabbix.
Attention ! Ceci peut provoquer des effets secondaires sur le résultat de la commande netstat. Ce désagrément reste cependant mineur.
Disques¶
Cache disque¶
Cette partie est détaillée dans le module J1 de notre formation PERF1.
Avant PostgreSQL 9.6¶
Le noyau Linux dispose d’un cache disque, en plus de celui que PostgreSQL maintient. Les blocs disques modifiés ne le sont pas immédiatement mais après un délai, pour améliorer la réactivité et le début en en écrivant plusieurs en même temps. La valeur Dirty
dans /proc/meminfo
indique la quantité de blocs « sales ».
Par défaut, Linux attend qu’un nombre assez grand de blocs soit modifié avant de lancer l’écriture sur disques en arrière-plan, ce qui peut durer plusieurs secondes, et gêner les opérations normales. Si la situation est tendue, il peut même demander à ce que les processus écrivent eux-mêmes, ce qui les mettra de plus en concurrence. On perd donc notamment l’effet de « lissage » du checkpoint.
On peut partir sur ces valeurs, déjà très inférieures aux valeurs par défaut, exprimées en pourcentage du cache disque de l’OS (pour simplifier) :
On descendra à 2 et 1 sur des configurations avec beaucoup de mémoire, ce qui peut représenter déjà des centaines de Mo.
Il faut tenir compte aussi de la rapidité des disques. Plus ils sont lents, plus les valeurs devront être abaissées.
Pour plus de clarté, on peut préférer des valeurs en octets :
Note
Si vous mettez en place une valeur en _bytes
, l’équivalent en _ratio
sera mis à 0
, et inversement.
À partir de PostgreSQL 9.6¶
Depuis PostgreSQL 9.6 et ses paramètres *_flush_after
, positionnés assez bas, la modification précédente est moins nécessaire : PostgreSQL forcera l’écriture assez vite, donc en quantité faible.
Par contre, le conseil précédent (pré-9.6) reste valable, notamment si l’on craint les pics d’écriture d’un pg_dump tournant sur le serveur.
Dans certains cas, on peut vouloir au contraire limiter au maximum les fsync demandés en dehors de PostgreSQL, donc en plaçant les valeurs très haut, au-delà des habituels 25% de la RAM octroyés aux shared buffers :
… puisque après tout, seul PostgreSQL est censé forcer l’écriture sur disque.
SSD¶
Si un disque est un SSD, il faut vérifier que le système l’a bien reconnu (/sys/block/sda/queue/rotational
à 0
). Au besoin, on le corrigera ainsi à chaque démarrage, et ce pour chaque disque (sda
, sdb
ou autre) :
Référence : https://lwn.net/Articles/408428/.
Scheduler disque¶
Dans une machine VIRTUELLE, il est préférable de désactiver le scheduler des entrées-sorties de l’OS, puisque l’hyperviseur s’en chargera. C’est souvent déjà le cas.
Voir https://access.redhat.com/solutions/5427, qui recommande pour une VM :
Il en est de même pour une machine physique pour certains SSD rapides, ou avec certaines baies, et de manière générale quand ce n’est pas à l’OS hôte de réordonner les accès disque parce que ce travail sera fait à un autre niveau (hyperviseur, baie…). Cette page recommande donc aussi none pour du stockage rapide, y compris NVMe.
Pour chaque disque physique :
Ces ordres doivent être rajoutés dans /etc/rc.local
, dans une ligne @reboot
de /etc/crontab
…
Attention
Afficher les valeurs de ces champs pour savoir s’il faut déclarer noop
ou none
! Cela dépend des kernels (consulter /sys/block/sda/queue/scheduler
).