Aller au contenu

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) :

sysctl -w vm.swappiness=10

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 :

sysctl -p fichier

ou, pour recharger tous les fichiers de configuration :

sysctl --system

CPU

Chapitre détaillé dans le module J1 de notre formation PERF1

Options NUMA

Pour voir la topologie d’un serveur :

numactl --hardware

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) :
numactl --interleave=all
  • 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 :

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) :

kernel.sched_autogroup_enabled=0

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 :

vm.swappiness=10

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 :

vm.overcommit_memory=2

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 :

CommitLimit = taille swap + (taille RAM * overcommit_ratio / 100)

soit :

overcommit_ratio = 80 - 100 (taille swap / taille RAM)

La valeur suivante convient en général pour les machines possédant peu de swap par rapport à la RAM :

vm.overcommit_ratio=80

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) :

$ cat /proc/sys/kernel/sem
250     32

ou :

ipcs -ls

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 :

SEMMNI= (100 + 3 + 8 + 5) /16 * 30 = 217

Pour modifier les valeurs :

kernel.sem = 250 32000 32 250

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 :

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 :

vm.nr_hugepages=0

et on préférera les huge pages dynamiques ; pour créer n pages (chacune de 2 Mo par défaut) :

vm.nr_overcommit_hugepages=n

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 :

transparent_hugepage=never

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.

net.ipv4.tcp_keepalive_time

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 :

net.ipv4.tcp_tw_reuse=1

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) :

vm.dirty_ratio=10
vm.dirty_background_ratio=5

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 :

vm.dirty_bytes=500000000
vm.dirty_background_bytes=250000000

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 :

vm.dirty_ratio=40
vm.dirty_background_ratio=30

… 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) :

echo 0 > /sys/block/sda/queue/rotational

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 :

Use mq-deadline. With a host bus adapter (HBA) driver that is multi-queue capable, use none

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 :

echo "noop" > /sys/block/sda/queue/scheduler
echo "noop" > /sys/block/sdb/queue/scheduler
...

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).

$ cat /sys/block/vda/queue/scheduler 
[none] mq-deadline kyber bfq