Overcommit¶
À propos
License | PostgreSQL |
---|---|
Compatibilité | Linux |
Résumé¶
Success
Le paramétrage de l’overcommit est nécessaire pour éviter des redémarrages de PostgreSQL en cas de saturation mémoire, et protéger le cache de Linux. Dans les cas simples (RAM importante, swap conseillé à 1 ou 2 Go, pas de huge pages), la configuration peut se résumer à :
Danger
Toute erreur dans le paramétrage peut mener à des erreurs mémoire, voire au plantage de Linux.
Nécessité du paramétrage du swap et de l’overcommit¶
Gestion de la mémoire¶
Par défaut, la configuration d’un serveur Linux autorise la réservation de toute la mémoire physique et du swap (mémoire virtuelle sur le disque), voire au-delà, même si elle n’est pas encore utilisée (allouée) par les applications.
Puis la mémoire physique est réellement utilisée (allouée). Quand elle commence à manquer, Linux rétrécit le cache, parfois jusqu’à épuisement. Or, la mémoire non utilisée par les applications sert de cache disque, et PostgreSQL se repose sur ce cache disque en plus de son propre cache (les shared buffers) pour la performance.
Parallèlement, Linux remplit le swap. Si celui-ci est trop gros (plusieurs Go), le remplir prendra un certain temps, pendant lequel le serveur sera très peu réactif (swap storm). Généralement, cela ne fait que reculer le moment où plus aucune mémoire physique ou virtuelle ne peut être allouée.
L’épuisement de la mémoire est donc catastrophique et les performances s’effondrent.
OOM Killer¶
Plus grave : dans le cas où le système n’arrive pas à honorer une demande mémoire qui aurait été déjà réservée préalablement, le noyau déclenche l’« Out-Of-Memory killer ». Pour libérer de la mémoire, celui-ci choisit des processus à tuer. Les processus de PostgreSQL consomment beaucoup de mémoire et sont donc de bons candidats pour être tués…
Les distributions modernes (depuis Debian 8, RedHat 7) protègent le processus principal
de PostgreSQL de l’OOM killer (paramétrage de OOMScoreAdjust
à -900 ou -1000
dans le service systemd
).
Mais ce processus principal surveille la santé de ses processus fils,
et si l’un d’eux (même un processus backend d’un client) est tué,
le processus principal veut éviter toute corruption de la mémoire partagée de
l’instance. Il décide donc de la réinitialiser et provoque un redémarrage de tous les
processus fils. Même si elle ne dure parfois que quelques secondes,
cette opération déconnecte tous les utilisateurs et arrête
toutes les requêtes en cours.
Consommation mémoire de PostgreSQL¶
-
shared_buffers = 4GB
provoque une réservation de 4 Go environ au démarrage de l’instance, qui n’est pas allouée d’entrée par le système, mais le sera plus ou moins rapidement lors du remplissage du cache, et ne sera plus libérée ensuite (sauf redémarrage de l’instance) -
la mémoire des processus est réservée et allouée rapidement (par exemple lors de gros tris) : le but est d’obtenir au pire l’arrêt des requêtes gourmandes
-
il existe de la mémoire partagée dynamique (et temporaire, pour les processus des requêtes parallélisées), qui ne sera pas traitée ici.
But de la configuration¶
Le but de la configuration vise à corriger le problème à la source :
- en forçant le serveur à n’utiliser que sa mémoire physique
- en refusant les nouvelles allocations mémoire qui ne pourraient être honorées (PostgreSQL sait gérer l’erreur).
On suppose un serveur 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. Il n’y a pas vraiment de solution dans ce cas.
Les paramètres du kernel Linux ci-dessous sont à renseigner dans
/etc/sysctl.conf
ou un fichier comme /etc/sysctl.d/90-postgresql.conf
,
et la configuration du kernel peut alors être rechargée avec :
sysctl
(ils disparaîtront au prochain reboot).
Par exemple :
# Consultation
$ sudo sysctl vm.swappiness
vm.swappiness = 60
$ cat /proc/sys/vm/swappiness
60
# Mise en place
$ sudo sysctl -w vm.swappiness=10
vm.swappiness = 10
Swap¶
L’utilisation du swap doit être découragée sur un serveur dédié à une base de données. Avec le « swappiness » à 10%, le swap ne sera plus utilisé qu’en dernier recours ou pour des processus d’arrière-plan rarement utilisés :
Il est déconseillé d’avoir un swap de plus de 1 ou 2 Go sur un serveur de bases
de données ! Un peu de swap reste utile par exemple pour des processus peu ou
plus utilisés, ou du tmpfs
, qui n’ont pas à encombrer la mémoire physique.
Tout le paramétrage suivant vise de toute manière à éviter d’utiliser le swap.
S’il est appliqué, un swap excessif reste un gaspillage de disque et complique
un peu les calculs.
Référence : https://www.kernel.org/doc/html/latest/admin-guide/sysctl/vm.html
Gestion de l’overcommit¶
Il s’agit de poser explicitement la limite sur la mémoire réservée par les applications, même non consommée. Les applications auront la garantie que la mémoire réservée sera disponible quand le système décidera de l’utiliser (de l’allouer). (Ce n’est pas possible par défaut car beaucoup d’applications réservent de la mémoire sans l’allouer réellement, par exemple les JVM Java. Une gestion stricte gaspillerait de la mémoire).
Linux calcule et stocke dans /proc/meminfo
:
CommitLimit
: limite de mémoire réservable, en fonction du paramétrage et de la configurationCommitted_AS
: la mémoire totale effectivement allouée à un moment donné, qui ne doit pas dépasserCommitLimit
Mise en place¶
Référence : https://www.kernel.org/doc/html/latest/mm/overcommit-accounting.html
Typiquement, on veut autoriser l’utilisation de 80% de la RAM physique
aux applications et réserver le reste de la RAM physique au système d’exploitation
et à son cache.
(Ce seuil est à arbitrer avec la valeur de work_mem
et la sensibilité au cache
de la base, la supervision sera utile pour trancher).
Nous supposerons par la suite un CommitLimit
cible de 80% de la RAM.
Cependant, on ne peut le définir directement, et il va falloir cibler et calculer les paramètres suivants pour que le noyau calcule lui-même cette valeur.
Le CommitLimit
se change ainsi :
-
D’abord, le paramètre noyau
vm.overcommit_memory
doit être passé de 0 (défaut, protection à base d’heuristique) à 2 (mode strict). -
Ensuite, la RAM effectivement utilisable par les applications se définit, au choix, avec
vm.overcommit_ratio
ouvm.overcommit_kbytes
. Le dernier modifié annule l’autre. Le premier (un simple ratio) a souvent la même valeur dans les cas simples, et tient compte de l’augmentation de la RAM. Le second (en ko) est plus simple à calculer dans les cas complexes, mais il faut impérativement le modifier lors d’un ajout de RAM au serveur (ce qu’il est très facile d’oublier). -
CommitLimit
est modifié instantanément quand ces paramètres sont modifiés, quand du swap est ajouté ou supprimé, ou quand la RAM est agrandie ou réduite. Le noyau additionne toujours le swap à la valeur calculée avec les paramètres ci-dessus.
Avertissement
Éviter de poser ces limites quand la RAM du serveur est chargée, car il est possible que plus aucune commande ne puisse démarrer, faute de mémoire, et qu’il faille redémarrer brutalement.
Les paramètres se calculent comme suit :
vm.overcommit_ratio¶
vm.overcommit_ratio
est un entier entre 0 et 100 lié au CommitLimit
par la formule :
vm.overcommit_ratio
dépend donc de la proportion
entre le swap et la RAM sur la machine, et doit donc être recalculé au besoin.
Le défaut est de seulement 50%.
Quand il y a beaucoup de RAM par rapport au swap et pas de Huge Pages,
un point de départ est :
Example
Sur un serveur avec 32 Go de RAM et 1 Go de swap, le kernel calculera
un CommitLimit
de 32 × 80% + 1 = 26,6 Go de RAM réservables,
garantissant ainsi que 5,4 Go (17% de la RAM) resteront libres pour l’OS.
On comprend que pour réserver plus de RAM au cache, il faudra
baisser un peu vm.overcommit_ratio
.
Pour un calcul précis, et tenant compte du swap et des éventuelles Huge Pages,
voir plus bas.
vm.overcommit_kbytes¶
Si vm.overcommit_kbytes
est défini,
le noyau ajoute juste le swap pour calculer
CommitLimit
.
Donc on peut définir vm.overcommit_kbytes
à 80% de 32 Go,
moins le swap :
# Pour 32 Go de RAM et 1 de swap
# 32 × 1024×1024 × 80/100 - 1023×1024 (ko)
vm.overcommit_kbytes=25795993
soit 24,6 Go d’où un CommitLimit
de 25,6 Go en ajoutant le swap,
et environ 6,4 Go réservés à l’OS (20% de la RAM).
Prise en compte du swap¶
Le kernel inclut toujours le swap dans les calculs de CommitLimit
ci-dessus alors que justement on ne voudrait pas que ce swap soit utilisé.
Il faut en tenir compte s’il représente plus de quelques pour cents
de la RAM physique (sinon vm.overcommit_ratio = 80
suffit).
Protéger 20% de la RAM physique revient à imposer :
La relation précédente entraîne :
vm.overcommit_ratio
= 80 - 100 × (taille swap / taille RAM)
ou :
vm.overcommit_kbytes
= 80/100 × taille RAM - swap (en ko)
Prise en compte des Huge Pages¶
Lorsque les huge pages (non transparentes)
sont activées sur le serveur, la méthode de calcul
est moins simple.
En effet, les huge pages ne sont pas concernées par la limite d’allocation,
il faut les retrancher du CommitLimit
, comme s’il y avait moins de RAM.
Typiquement, avec PostgreSQL, le nombre de huge pages suffit à couvrir juste
la taille de la mémoire partagée.
Donc la cible de mémoire réservable devient :
où HP
est le produit de ces valeurs provenant de /proc/meminfo
:
HugePages_Total
: nombre de huge pages réellement réservées, à savoir toutes les Huge Pages statiques (paramètrevm.nr_hugepages
), utilisées ou non, plus la portion des pages « dynamiques » (paramètrevm.nr_overcommit_hugepages
) qui ont été allouées, et couvrant en pratique un peu plus queshared_buffers
(mais dépendent de certains paramétrages) ;Hugepagesize
: taille des huge pages utilisées (par défaut 2 Mo sur Linux pour les architectures x86_64).
Et sachant que le noyau calcule ainsi :
le paramétrage devient (toujours pour 20% de cache OS préservé) : ou alternativement : (Voir les exemples)Cas des instances multiples¶
Si plusieurs instances fonctionnent simultanément, le paramétrage de l’overcommit est équivalent à une unique instance possédant la somme des shared buffers.
Il faut configurer les shared_buffers
de chaque instance
au plus juste selon leur utilisation (le total respectant la règle
d’¼ à ⅓ de la RAM).
Prise en compte d’autres applications¶
S’il est nécessaire de faire fonctionner d’autres applications sur le même serveur,
le calcul de l’overcommit reste identique,
mais il faut réduire shared_buffers
(et effective_cache_size
et work_mem
)
dans postgresql.conf
selon les besoins mémoire de ces autres applications.
À l’inverse, ces applications doivent tenir compte des besoins de PostgreSQL
en période de forte charge. Suivant les applications,
il peut même arriver qu’il soit impossible de définir un CommitLimit
(si une application alloue énormément de mémoire sans s’en servir).
Valeurs courantes et exemples¶
RAM | Swap | Huge Pages (2 Mo) | CommitLimit recherché | vm.overcommit_ratio | vm.overcommit_kbytes |
---|---|---|---|---|---|
0 | Non | 80% à 90% RAM | 80 (pour préserver 20% du cache) à 90 (pour préserver 10%) | Préférer le ratio | |
< 10% de la RAM | Non | 80% RAM + swap | 80 (pour préserver 20% du cache) | Préférer le ratio | |
< 10% de la RAM | 25% de la RAM (shared_buffers ) |
80% RAM-HP | Entre 60 et 72 (pour préserver 20% du cache) | Voir formule | |
8 Go | 2 Go | Non | 6,4 Go | 55 | 4 613 734 ko |
32 Go | 2 Go | Non | 25,6 Go | 77 | 24 746 394 ko |
32 Go | 1 Go | 4500 pages (8,8 Go) | 16,8 Go | 68 | 16 578 970 ko |
64 Go | 2 Go | 8600 pages (16,8 Go) | 32,4 Go | 69 | 33 977 139 ko |
128 Go | 2 Go | 18000 pages (35,2 Go) | 67,2 Go | 70 | 68 413 030 ko |
Dans les exemples ci-dessous, on n’oublie pas que 1 Go = 1024 x 1024 ko. D’autre part, de petits écarts peuvent provenir de l’arrondi dans les ratios, ou de petits écarts entre la réalité et les valeurs « rondes » en Go définies pour la RAM et le swap.
Exemple 1¶
Sur un serveur avec 32 Go de RAM et 1 Go de swap :
shared_buffers
vaut par exemple 8 Go (¼ de la RAM)- on choisit de ne pas paramétrer les Huge Pages
- on veut préserver 20% de la RAM pour l’OS et son cache, soit 6,4 Go
- donc le
CommitLimit
cible est 80% de la RAM = 25,6 Go
D’où :
vm.overcommit_ratio
= 80 - 100 × (1 / 32) = 77 (arrondi)- d’où
CommitLimit
= 1 + 32 × 77/100 = 25,6 Go
- d’où
Ou :
vm.overcommit_kbytes
= 80% RAM - swap = 24,6 Go = 25 794 970 ko
Résumé :
Résultat sur une machine réelle (avec le ratio ici) :
$ free -m
total used free shared buff/cache available
Mem: 32097 9035 7297 8375 24595 23062
Swap: 1023 0 1023
$ grep -E '(Swap|Commit|Huge)' /proc/meminfo
…
SwapTotal: 1048572 kB
SwapFree: 1048572 kB
CommitLimit: 26356580 kB
Committed_AS: 8747824 kB
…
HugePages_Total: 0
Exemple 2¶
Sur le même serveur avec 32 Go de RAM et 1 Go de swap :
shared_buffers
vaut 8 Go (¼ de la RAM)- pour les Huge Pages, on prévoit au moins 8 x 1024 Mo / 2 Mo = 4069 huges pages de 2 Mo, donc plutôt 4500 avec une sécurité de 10 %, soit 8,8 Go (9000 Mo)
- on veut préserver 20% de la RAM pour l’OS et son cache, soit 6,4 Go
- donc le
CommitLimit
cible est : 80% × 32 Go - 8,8 Go = 16,8 Go
D’où :
vm.overcommit_ratio
= (80% x 32 - 1 - 8,8) / (32 - 8,8) = 68%- d’où
CommitLimit
= 1 + ( 32 - 8,8 ) × 68/100 = 16,8 Go
- d’où
- ou bien :
vm.overcommit_kbytes
= (80% × 32 - 1 - 8,8) = 15,8 Go ( ou 32×1024×1024×80/100 - 1023×1024 - 4500×2×1024 = 16 579 993 ko)- et
CommitLimit
= 1 + 15,8 Go = 16,8 Go (17 628 567 ko)
- et
Résumé :
# huge pages : statiques ou dynamiques
vm.nr_hugepages=4500
vm.nr_overcommit_hugepages=0
vm.overcommit_memory = 2
# au choix
vm.overcommit_ratio = 68
vm.overcommit_kbytes = 16579993
On a donc bien 16,8 Go utilisables pour les requêtes + 6,4 Go pour l’OS et son cache + 8,8 Go pour la mémoire partagée de PostgreSQL = 32 Go de RAM physique.
Résultat sur une machine réelle (avec le ratio) :
$ free -m
total used free shared buff/cache available
Mem: 32097 9543 9804 2 13148 22554
Swap: 1023 0 1023
$ cat /proc/meminfo
MemTotal: 32867548 kB # 31,3 Go en réalité
MemFree: 10040104 kB
MemAvailable: 23095300 kB
Buffers: 15300 kB
Cached: 13368108 kB
SwapCached: 0 kB
…
SwapTotal: 1048572 kB
SwapFree: 1048572 kB
…
CommitLimit: 17131624 kB
Committed_AS: 135992 kB
…
HugePages_Total: 4500
HugePages_Free: 310
HugePages_Rsvd: 10
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 9216000 kB
DirectMap4k: 89964 kB
DirectMap2M: 5152768 kB
DirectMap1G: 30408704 kB
Exemple 3¶
Avec :
- 16 Go de RAM, 4 Go de shared buffers
- 8 Go de swap (valeur excessive non recommandée)
- pas de Huge Pages
- 3,2 Go (20%) de la RAM à réserver
Alors :
vm.overcommit_memory = 2
# 80 - 100 × (8/16)
vm.overcommit_ratio = 30
# CommitLimit sera 13421772 kB (12,8 Go)
# avec 3,2 Go de mémoire réservée au système et au cache
# au choix
vm.overcommit_memory = 2
# 16 × 80% - 8 Go (en ko)
vm.overcommit_kbytes = 5033164
# CommitLimit : 13421768 kB (12,8 Go)
Avec des huge pages :
vm.nr_overcommit_hugepages=2150
vm.nr_hugepages=0
vm.overcommit_memory = 2
# au choix :
# 100 × (80/100 × 16×1024 - 8×1024 - 2150×2) / (16×1024 - 2150×2)
vm.overcommit_ratio = 5
# 16 × 80% - 8 Go - 2100 × 2 Mo (en ko)
vm.overcommit_kbytes = 732365
# CommitLimit : (8,7 Go)
On obtient, une fois les shared buffers remplis :
$ free -m
total used free shared buff/cache available
Mem: 15993 4665 6897 2 4754 11328
Swap: 8191 0 8191
$ cat /proc/meminfo
MemTotal: 16377064 kB # 15,6 Go en réalité
MemFree: 7062968 kB
MemAvailable: 11600064 kB
…
SwapTotal: 8388604 kB
SwapFree: 8388604 kB
…
CommitLimit: 9120968 kB
Committed_AS: 141444 kB
…
HugePages_Total: 2107
HugePages_Free: 129
HugePages_Rsvd: 129
HugePages_Surp: 2107
Hugepagesize: 2048 kB
…
Avec free -m
on peut vérifier que la mémoire disponible (available)
ne descend jamais en-dessous des 3200 Mo attendus.
Exemple 4¶
Avec :
- 16 Go de RAM, 4 Go de shared buffers
- 16 Go de swap (valeur très excessive non recommandée)
- pas de Huge Pages
Le calcul de vm.overcommit_ratio
ou vm.overcommit_kbytes
donnerait un chiffre négatif, ce qui n’est pas possible.
La seule solution recommandée est de réduire le swap à 1 ou 2 Go.
Exemple 5¶
Un serveur a cette configuration :
- 6 Go de RAM
- 4 Go de swap (valeur un peu excessive)
- 1 Go de shared buffers
- 560 huge pages (1,1 Go)
vm.overcommit_ratio=80
Le CommitLimit
est dans ce cas de (6×1024-560×2)×80/100+4×1024 = 8115 Mo,
ce qui dépasse la mémoire physique et permet l’utilisation d’une bonne partie
du swap, heureusement sans provoquer son épuisement. Sur un disque rapide
une requête trop consommatrice tombe vite en erreur, mais le cache disque
est purgé.
Paramétrer vm.overcommit_ratio
ne permet pas de protéger 20% de la RAM,
la valeur serait négative.
Au mieux, vm.overcommit_ratio=0
donne un CommitLimit
de 4 Go. Comme il y a aussi 1 Go pour les shared buffers, ce paramétrage
protège 1 Go pour le cache de Linux (15% de la RAM).
Manifestations du dépassement de capacité¶
Il est difficile d’estimer quelle valeur de Committed_AS
sera atteinte :
le calcul de la mémoire réservée sous Linux est très délicat à cause de la
mutualisation.
La mémoire consommée par chaque processus est visible dans /proc/<PID>/smaps
.
Côté PostgreSQL, rappelons que la consommation mémoire d’une session cliente
n’est pas forcément limitée par la valeur du work_mem
.
Ce paramètre concerne les tris et les tables de hachages,
et il peut y en avoir plusieurs dans une requête complexe.
D’autre part la session a besoin de mémoire pour les valeurs récupérées.
L’optimiseur peut aussi mal estimer certains besoins et consommer
beaucoup plus de mémoire que prévu.
Etc. (Pour les détails, voir PostgreSQL and RAM usage).
Quand Committed_AS
atteint la CommitLimit
, les sessions se voient refuser
leurs nouvelles allocations mémoire, et il n’est plus possible de créer
de nouveau processus, l’autovacuum ne peut plus se déclencher.
Le journal de PostgreSQL se remplit d’erreurs comme :
Mais les sessions en cours ne se déconnectent pas, et le serveur ne s’arrête pas.
Il est à noter qu’il est intéressant de superviser les valeurs relatives de
CommitLimit
et CommittedAS
afin de tenter de prévenir ce genre d’incident
avant son occurrence.
Tester le bon fonctionnement du paramétrage de l’overcommit¶
Pour tester le nouveau paramétrage :
- attendre un moment où le serveur n’est pas utilisé ;
- vérifier avec
sysctl -a vm.*
que les paramètres voulus sont bien appliqués ; - pour voir l’occupation du swap et la disparition du cache,
lancer dans une console cet outil, et suivre notamment les colonnes
swpd
,free
etcache
:
$ vmstat --wide -SM 1
procs -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 50 2598 0 45 0 0 0 0 287 102 10 1 89 0 0
2 0 50 2212 0 45 0 0 0 0 1063 97 46 3 50 0 1
1 0 50 1823 0 45 0 0 0 0 1035 68 47 2 51 0 0
1 0 50 1461 0 45 0 0 0 0 1054 88 46 3 51 0 0
1 0 50 1096 0 45 0 0 0 0 1046 79 46 3 50 0 1
- ou bien, sur un système bien configuré, on peut suivre l’évolution de
free
, et vérifier quecache
etavailable
ne descendent pas en-dessous des 20% (par exemple) que l’on a voulu préserver :
$ watch -n 0.5 free -m
total used free shared buff/cache available
Mem: 15993 4670 7849 2 3786 11323
Swap: 8191 5 8186
- dans ces outils, noter que les shared buffers sont comptabilisés
soit dans
shared
(cas général) soit dansused
(si les huge pages sont configurées pour la mémoire partagée) ; - depuis
psql
ou un autre client, tenter de trier ½ To de données en RAM :
SET work_mem = '1000GB' ;
EXPLAIN (ANALYZE) SELECT i FROM generate_series (1,1e10) i
ORDER BY i DESC ;
Soit le serveur est bien paramétré, et la session tombera assez rapidement en erreur, mais sans emporter les autres sessions, ni purger le cache OS, avec juste cette erreur :
Soit le serveur est mal paramétré et le swap va se remplir, ralentissant tout (surtout si le cache est gros) ;
puis PostgreSQL redémarrera en coupant toutes les connexions,
et avec ce genre de messages dans les traces systèmes ou dmesg
:
Spécificités des versions antérieures à la 9.3¶
Sur les anciens serveurs jusqu’en version 9.2 comprise, kernel.shmall
et kernel.shmmax
déterminent la quantité maximale de mémoire partagée allouable. kernel.shmmax
définit la taille maximale d’un segment de mémoire partagée allouable par un processus.
Sa valeur doit être supérieure à celle nécessaire à PostgreSQL.
Par défaut, les valeurs de ces paramètres sont positionnées à des valeurs largement
suffisantes sur les distributions de type Red Hat.
Depuis la version 9.3, PostgreSQL utilise l’allocation de mémoire utilisateur au travers
de l’appel à mmap
ce qui ne nécessite plus l’augmentation de ces paramètres du noyau.
Ces paramètres peuvent donc être laissés tels quels, cela n’a pas d’incidence
sur le comportement de PostgreSQL.
Aller plus loin¶
- kernel.org: Documentation for /proc/sys/vm/
- Documentation de PostgreSQL : Linux memory overcommit
- PostgreSQL and RAM usage (Alexey Bashtanov), sur la gestion d’½ To de RAM