Huge pages¶
À propos
License | PostgreSQL |
---|---|
Compatibilité | Linux |
Résumé¶
Recommandation
Dalibo recommande la mise en place des huge pages de 2 Mo sur tout serveur Linux bien doté en RAM ou avec de nombreux processus clients.
Le gain en performance est généralement modeste mais réel, il peut y avoir des économies de mémoire, et la stabilité est meilleure.
Nous préconisons d’utiliser des pages dynamiques pour couvrir toute la
mémoire partagée (en général ¼ de la RAM),
via le paramètre noyau vm.nr_overcommit_hugepages
et de passer le paramètre PostgreSQL huge_pages
à on
.
Les Transparent Huge Pages sont par contre à désactiver.
Attention
Il faut vérifier la bonne utilisation des huge pages, et en tenir compte lors du calcul de l’overcommit. Prévoir une marge en cas de changement de paramétrage de l’instance.
Utilité du paramétrage des huge pages¶
Adressage, MMU et TLB¶
Les systèmes d’exploitation modernes utilisent de la mémoire « virtuelle » : les adresses connues d’un programme ne sont pas celles réelles. Le processeur convertit les adresses connues par le processus en adresses physiques (une page et une adresse dans la bonne barrette mémoire) au moyen de son MMU (Memory Management Unit).
Le MMU enregistre chacune de ces correspondances dans un cache matériel spécifique (un par core), nommée TLB (Translation Lookaside Buffer).
Le TLB dépend du processeur et possède plusieurs niveaux. Pour être le plus rapide possible, il est petit. Pour chaque processus, une table de pagination (PTE), écrite et maintenue en RAM, vient compléter le TLB.
Quand une correspondance n’est pas trouvée directement dans le TLB, on parle de TLB miss. Ce n’est bien sûr pas idéal pour les performances, selon que l’on doit chercher dans la table de pagination (qui vit en RAM, mais dont une partie peut se trouver dans les caches L1, L2 ou L3 du processeur) ou sur disque (swap ou système de fichiers). Le hit ratio du TLB est donc important.
À chaque context switch, le TLB en cours est vidé (TLB flush), et la table de pagination du nouveau processus chargée. Au final, cela fonctionne bien s’il n’y a pas trop de RAM, ou peu de processus.
Or, les bases de données sont particulières, avec des allocations parfois en gigaoctets. Or, 1 Go correspond à 262 144 pages de 4 ko. L’appel d’un bloc précis du cache de PostgreSQL a donc une chance infime de se trouver dans le TLB en place, et il faut aller chercher l’adresse mémoire en-dehors. De plus, les bases de données génèrent énormément de context switches menant à autant de TLB flush.
Rôle des huge pages¶
L’idée est de ne plus avoir de table de correspondance par page de 4 ko, mais par 2 Mo voire 1 Go (valeurs sur processeurs Intel), pour réduire les TLB flush et économiser l’essentiel de la mémoire des tables de pagination (voir Gains de performance).
Avec des pages de 2 Mo, il ne faut plus que 512 entrées pour couvrir 1 Go au lieu de 262 144 : la table de pagination plus petite entraîne un bien meilleur hit ratio sur le TLB. C’est encore mieux avec des pages de 1 Go. Le premier avantage des huge pages est donc cette amélioration des performances du TLB.
D’autre part, on peut économiser de la mémoire.
En effet, avec beaucoup de processus de longue durée de vie, même dormants,
comme les nombreux backends de PostgreSQL,
l’addition de toutes les tables de pagination peut représenter une
belle quantité. Elle est calculée dans /proc/meminfo
:
Enfin, les huge pages ne sont pas « swappables ». Comme les shared buffers ne doivent jamais être swappés (c’est un cache disque !), c’est un avantage. Le swap n’utilise pas de huge pages.
Ce qui suit se concentrera sur les huge pages de 2 Mo. Celles à 1 Go n’ont pas d’intérêt supplémentaire en performance et sont moins flexibles.
Prérequis matériels¶
- Pour l’architecture x86_64,
le processeur doit afficher les flags
pse
(pages à 2 Mo) etpdpe1gb
(pages à 1 Go). Ce devrait être le cas de tout serveur installé dans la dernière décennie.
- L’architecture arm64 supporte ces deux tailles de page.
Paramétrage PostgreSQL¶
PostgreSQL possède ce paramètre depuis la version 9.4 :
-
La valeur par défaut est
try
: PostgreSQL tente d’allouer des huge pages pour toute sa mémoire partagée (principalement lesshared_buffers
). En cas d’échec, même s’il ne manque que quelques pages, PostgreSQL se rabat sur la taille de page par défaut pour toute sa mémoire partagée (il n’y a pas de message dans les traces !). C’est la valeur à préférer avec des huge pages dynamiques. -
on
exige la présence de toutes les huge pages nécessaires. En cas d’échec, PostgreSQL ne démarre pas avec ce message :C’est la valeur à préférer avec des huge pages statiques.FATAL: could not map anonymous shared memory: Cannot allocate memory HINT: This error usually means that PostgreSQL's request for a shared memory segment exceeded available memory, swap space, or huge pages. To reduce the request size (currently 1635778560 bytes), reduce PostgreSQL's shared memory usage, perhaps by reducing shared_buffers or max_connections.
-
off
exige des pages normales, ce qui fonctionne toujours s’il reste de la mémoire.
Paramétrage Linux¶
Calcul du nombre de pages¶
Le nombre de pages pour la mémoire partagée de PostgreSQL dépend de divers
paramètres : shared_buffers
bien sûr, mais aussi max_connections
et d’autres. Le nombre de pages nécessaires peut donc changer suite à un
simple petit changement de configuration.
Attention
En conséquence, il faut calculer ce nombre de pages une fois la configuration de PostgreSQL faite, et/ou prévoir une marge.
Il faut aussi tenir compte des huge pages que d’autres logiciels sur le serveur nécessiteraient (l’idéal étant un serveur dédié à PostgreSQL).
Par défaut, PostgreSQL utilise la taille par défaut des huge pages du
système (voir le paramètre huge_page_size
à partir de PostgreSQL 14).
Avec PostgreSQL 15 et supérieur¶
PostgreSQL indique lui-même le nombre de pages voulues
(mais pas forcément encore utilisées !)
avec shared_memory_size_in_huge_pages
:
postgres=# SHOW shared_buffers ;
shared_buffers
----------------
16GB
postgres=# SHOW shared_memory_size ;
shared_memory_size
--------------------
17253MB
postgres=# SHOW shared_memory_size_in_huge_pages ;
shared_memory_size_in_huge_pages
----------------------------------
8627
Avec PostgreSQL 14 et précédents¶
La documentation officielle donne une méthode de calcul à partir d’un
pmap <PID du postmaster>
en ko, divisée en pages de 2 Mo (2048 ko).
Exemple de calcul du nombre de pages à partir de pmap
D’où 17666696 / 2048 = 8626,3 pages, soit le 8627 indiqué plus haut.Plus grossièrement, on peut aussi partir de la taille des
shared buffers
et appliquer une sécurité de 10 % pour tenir compte
des autres segments de mémoire partagée.
Il vaut mieux prévoir un petit excès, car une modification de
max_connections
ou certains autres paramètres peut agrandir
la mémoire partagée. Si les pages sont dynamiques (voir plus bas)
l’excès de pages ne sera pas pour autant gaspillé.
Exemple de calcul à partir de shared_buffers
16 Go de shared buffers donnent 1,1×(16×1024×1024)/(2×1024) = 9011 pages de 2 Mo (un peu plus que ci-dessus).
Évidemment une augmentation de shared_buffers
doit amener à revoir ce calcul.
Pages statiques¶
Définir le nombre de pages statiques voulues
dans /etc/sysctl.conf
ou sous /etc/sysctl.d/
(ou utiliser sysctl -w
) :
- Les pages statiques bloquent immédiatement la mémoire.
- Elles sont la seule méthode si l’on tient à des pages de 1 Go.
Attention
Le noyau crée immédiatement les pages qu’il peut, mais il n’y arrive pas toujours, et il n’y a alors pas d’erreur ! (voir plus bas pour les vérifications)
À l’inverse, en cas de réduction du nombre de pages, les pages déjà utilisées par des applications ne sont pas libérées avant l’arrêt de ces applications.
Pages dynamiques¶
De la même manière :
Si le nombre de pages demandées ici est plus grand que ce que réserve ensuite PostgreSQL, l’excès reste en pages standard disponibles pour l’utilisation normale. (Il vaut tout de même mieux éviter de grands écarts en raison des calculs d’overcommit.)
Pages statiques ou dynamiques ?¶
Certes, il est techniquement possible de définir un mélange de pages statiques et dynamiques, qui pourront être utilisées simultanément. Mais on ne veut généralement qu’un type ou l’autre.
Pour éviter toute erreur, il vaut donc mieux définir systématiquement les deux paramètres, en en mettant un à zéro :
Recommandation
Dalibo recommande les pages dynamiques de 2 Mo :
- C’est la méthode la plus simple.
- Elle permet de prévoir une marge en cas de changement de configuration sans gaspiller de mémoire.
- Elle permet de positionner
huge_pages=try
: si des huge pages dynamiques manquent, PostgreSQL démarre tout de même sans les utiliser, aucune huge page ne sera utilisée, la mémoire ne sera pas gaspillée (l’impact de ce mode légèrement dégradé est généralement faible). - À l’inverse, les pages statiques imposent
huge_pages=on
: s’il manque des huge pages statiques, PostgreSQL ne les utilise pas, mais elles bloquent tout de même le quart de la RAM qu’on aurait voulu octroyer pour les shared buffers, ce qui n’est pas tolérable. Il vaut mieux alors que PostgreSQL ne démarre même pas.
Pages de 1 Go¶
Des huge pages de 1 Go sont forcément statiques. Elles n’ont peut-être un intérêt que pour les plus grosses configurations mémoire.
Sur x86_64, il faut démarrer Linux avec ce paramétrage :
(à ajouter généralement dans/etc/default/grub
,
ligne GRUB_CMDLINE_LINUX
, mais la procédure exacte
dépend de la distribution et de sa version, voire
de la configuration du fournisseur cloud ; ne pas oublier de
réinstaller grub
avant de redémarrer).
Définir le nombre de pages (ci-dessous 9) permet de parer, dès
le démarrage du système, à toute fragmentation mémoire.
L’allocation statique via vm.nr_hugepages
reste modifiable
de la manière habituelle, contrairement à l’allocation
dynamique avec vm.nr_overcommit_hugepages
.
PostgreSQL en tient compte dans le nombre de pages qu’il préconise :
postgres=# SHOW shared_buffers ;
shared_buffers
----------------
8GB
postgres=# SHOW shared_memory_size_in_huge_pages ;
shared_memory_size_in_huge_pages
----------------------------------
9
Après que PostgreSQL a démarré et a rempli ses shared buffers, ceux-ci utilisent bien les huge pages :
$ grep Huge /proc/meminfo
AnonHugePages: 2048 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 9
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 1048576 kB
Hugetlb: 9437184 kB
Il est possible de mélanger des pages de taille différente mais ça n’a pas d’intérêt dans le cas de PostgreSQL.
Désactivation des Transparent Huge Pages¶
Quand le noyau Linux détecte une allocation contiguë de mémoire, il peut la convertir en transparent huge pages (THP) automatiquement.
Les THP ont mauvaise réputation dans le monde des bases de données, car des latences1 importantes ont pu être observées. Cette mauvaise réputation n’a peut-être plus lieu d’être grâce aux nombreuses améliorations à ce sujet dans les versions récentes du noyau Linux. Par prudence, et avant qu’un nouveau consensus se dégage dans la communauté PostgreSQL, nous préférons toutefois nous en tenir à notre recommandation de toujours, qui est de désactiver celles-ci, en particulier pour les noyaux linux précédant la version 4.112.
La quantité de RAM soumise aux THP est visible dans /proc/meminfo
:
# sous root
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
Pour que ce paramétrage soit encore présent après reboot, il y a plusieurs manières.
On peut ajouter ceci dans /etc/crontab
:
@reboot root echo never > /sys/kernel/mm/transparent_hugepage/enabled
@reboot root echo never > /sys/kernel/mm/transparent_hugepage/defrag
ou dans /etc/rc.local
, ou dans un timer systemd ;
ou encore, dans la configuration de grub
, en ajoutant
ceci à la ligne de commande de Linux :
/etc/default/grub
,
ligne GRUB_CMDLINE_LINUX
, mais la procédure exacte
dépend de la distribution et de sa version, voire
de la configuration du fournisseur cloud ; ne pas oublier de
réinstaller grub
avant de redémarrer) ;
ou encore en utilisant un outil comme tuned
(sur distributions dérivées de Red Hat).
Contrôler l’utilisation des huge pages¶
La mémoire se fragmente vite, et le noyau échoue parfois à trouver les pages nécessaires. Il faut vérifier qu’il a réussi.
-
Les valeurs en cours des deux paramètres précédents sont visibles dans
/proc/sys/vm/nr_hugepages
et/proc/sys/vm/nr_overcommit_hugepages
(ou avecsysctl -a
). Elles peuvent différer de ce qui a été demandé. -
/proc/meminfo
est plus complet :
Signification (d’après la documentation de Linux) :
Ligne de /proc/meminfo |
Utilité |
---|---|
AnonHugePages |
Quantité de Transparent Huge Pages pour les allocations mémoire ; devrait être à 0 |
ShmemHugePages |
Quantité de Transparent Huge Pages pour la mémoire partagée; devrait être à 0 |
FileHugePages |
Quantité de Transparent Huge Pages pour les mappings de fichiers; devrait être à 0 |
HugePages_Total |
Nombre total de pages (dynamiques utilisées + statiques) |
HugePages_Rsvd |
Pages réservées mais non encore allouées par l’application, qu’elle a la garantie de pouvoir allouer le moment venu (1) |
HugePages_Surp |
(« surplus ») pages dépassant /proc/sys/vm/nr_hugepages , le maximum étant /proc/sys/vm/nr_overcommit_hugepages (en pratique : le nombre de pages dynamiques utilisées) |
Hugepagesize |
Taille par défaut des huge pages sur le système |
Hugetlb |
Taille mémoire totale consommée par les huge pages de toute taille |
(1) HugePages_Free
et HugePages_Rsvd
peuvent être élevés
au démarrage de PostgreSQL, quand celui-ci a réservé les pages
mais n’a pas encore rempli les shared buffers.
En cas d’échec de création des pages :
- répéter l’ordre
sysctl
plusieurs fois hugeadm
peut aider (voir plus bas)- rebooter après avoir renseigné la configuration dans
/etc/sysctl.d/
est la solution la plus radicale et efficace.
Outils¶
Libération de pages¶
Si la mémoire est fragmentée, les huge pages demandées ne seront pas forcément disponibles. Pour libérer la place avant de retenter leur création, diverses solutions complémentaires peuvent être testées, comme :
- purge du cache OS :
- arrêt provisoire de toutes les applications ou presque
- demande d’une défragmentation (par expérience pas très efficace) :
- utilisation de
hugeadm
(voir ci-dessous) - en désespoir de cause, un reboot.
hugeadm¶
hugeadm est packagé pour les principales distributions, mais pas installé par défaut :
- Debian 10/11/12 : paquet
libhugetlbfs-bin
(éventuellementhugepages
sur Ubuntu avant la version 20.04) - CentOS/Rocky Linux 7/8 : paquet
libhugetlbfs-utils
- RHEL/Rocky Linux 9 : le package contenant l’outil n’est plus disponible.
Exemple :
$ sudo hugeadm --pool-list
Size Minimum Current Maximum Default
2097152 2000 6985 10000 *
1073741824 0 0 0
On y voit un minimum de 2000 pages statiques (vm.nr_hugepages
),
une valeur maximale de 10 000 (vm.nr_hugepages
+ vm.nr_overcommit_hugepages
),
et 6985 pages réellement affectées.
La commande suivante tente de monter vm.nr_hugepages
à 20 000
malgré la fragmentation.
L’outil retente tant qu’il arrive à trouver des pages. Comme on le voit,
il n’est pas garanti qu’il y arrive.
$ sudo hugeadm -v --pool-pages-min DEFAULT:20000 --hard
hugeadm:INFO: page_size<DEFAULT> adjust<20000> counter<0>
hugeadm:INFO: 9000, 20000 -> 20000, 20000
hugeadm:INFO: setting HUGEPAGES_TOTAL to 20000
hugeadm:INFO: Retrying allocation HUGEPAGES_TOTAL to 20000 current 18895
hugeadm:INFO: Retrying allocation HUGEPAGES_TOTAL to 20000 current 18895
hugeadm:INFO: Retrying allocation HUGEPAGES_TOTAL to 20000 current 18899
hugeadm:INFO: Retrying allocation HUGEPAGES_TOTAL to 20000 current 18899
hugeadm:INFO: Retrying allocation HUGEPAGES_TOTAL to 20000 current 18899
hugeadm:INFO: Retrying allocation HUGEPAGES_TOTAL to 20000 current 18899
hugeadm:INFO: Retrying allocation HUGEPAGES_TOTAL to 20000 current 18899
hugeadm:INFO: Retrying allocation HUGEPAGES_TOTAL to 20000 current 18899
hugeadm:WARNING: failed to set pool minimum to 20000 became 18899
hugeadm:INFO: setting HUGEPAGES_OC to 1101
$ sudo hugeadm --pool-list
Size Minimum Current Maximum Default
2097152 18899 18899 20000 *
1073741824 0 0 0
Quels processus utilisent les huge pages ?¶
Noter qu’attribuer des huge pages à un processus précis n’a guère de sens car il s’agit de mémoire partagée entre les différents processus de PostgreSQL.
La commande suivante va chercher la mention des huge pages dans chaque
entrée de processus /proc/<PID>
, et affiche les processus correspondants.
On peut voir deux instances PostgreSQL et un serveur Apache.
(S’il n’y a aucune huge page, la commande ps
tombera en erreur)
$ ps -hq $(echo $(sudo grep huge /proc/*/numa_maps 2>/dev/null|cut -f 3 -d'/'|sort -n) |sed 's/ /,/g')
3612 ? S 0:29 /usr/lib/postgresql/9.6/bin/postgres -D /var/lib/postgresql/9.6/infra -c config_file=/etc/postgresql/9.6/infra/postgresql.conf
4111 ? Ss 0:19 postgres: 9.6/infra: checkpointer process
4112 ? Ss 0:00 postgres: 9.6/infra: writer process
4113 ? Ss 0:08 postgres: 9.6/infra: wal writer process
4114 ? Ss 0:03 postgres: 9.6/infra: autovacuum launcher process
4173 ? Ss 0:00 postgres: 9.6/infra: temboard temboard [local] idle
5935 ? Ss 0:59 postgres: 9.6/infra: icinga2 icinga2 127.0.0.1(54732) idle in transaction
24995 ? Ss 0:00 /usr/sbin/apache2 -k start
26776 ? Ss 0:01 /usr/lib/postgresql/12/bin/postgres -D /var/lib/postgresql/hdd/12/huge -c config_file=/etc/postgresql/12/huge/postgresql.conf
26841 ? Ss 0:00 postgres: 12/huge: checkpointer
26842 ? Ss 0:00 postgres: 12/huge: background writer
26843 ? Ss 0:00 postgres: 12/huge: walwriter
26844 ? Ss 0:00 postgres: 12/huge: autovacuum launcher
26847 ? Ss 0:00 postgres: 12/huge: logical replication launcher
28360 ? Rs 0:02 postgres: 9.6/infra: postgres opm [local] SELECT
32708 ? Sl 0:00 /usr/sbin/apache2 -k start
32709 ? S 0:01 /usr/sbin/apache2 -k start
32710 ? S 0:00 /usr/sbin/apache2 -k start
32711 ? S 0:00 /usr/sbin/apache2 -k start
32712 ? S 0:00 /usr/sbin/apache2 -k start
32713 ? S 0:00 /usr/sbin/apache2 -k start
32714 ? S 0:00 /usr/sbin/apache2 -k start
Les huge pages sont-elles bien utilisées ?¶
Globalement¶
Il faut vérifier que les huge pages sont bien utilisées. Ce n’est pas si facile car :
- Linux ne tombe pas en erreur s’il n’arrive pas à les allouer
- par défaut, PostgreSQL ne tombe pas en erreur s’il ne les trouve pas,
sauf à changer
huge_pages
àon
- les valeurs affichées diffèrent selon que les shared buffers sont remplis ou non.
Les outils sont surtout /proc/meminfo
,
éventuellement hugeadm
ou numastat
(paquet numactl
)
pour une vision un peu plus lisible.
free -m
est un complément utile.
Sans huge pages¶
Si les huge pages ne sont pas configurées, et si les shared buffers
(1,5 Go ici) sont pleins, la mémoire consommée par ceux-ci s’affiche dans
used
et shared
:
Avec des pages statiques¶
On suppose une machine de 6 Go de RAM, avec 1,5 Go de shared buffers et ces paramètres :
PostgreSQL arrêté : les pages sont déjà considérées comme utilisées, même si elles sont libres :
sudo grep Huge /proc/meminfo
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 800
HugePages_Free: 800
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 1638400 kB
PostgreSQL démarré, on voit qu’il a bien réservé ses pages, mais elles sont presque toutes libres :
$ sudo grep Huge /proc/meminfo
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 800
HugePages_Free: 771
HugePages_Rsvd: 751
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 1638400 kB
Une fois les shared buffers pleins (pg_prewarm
permet d’accélérer cela au besoin), il n’y a presque plus de pages libres :
$ sudo grep Huge /proc/meminfo
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 800
HugePages_Free: 22
HugePages_Rsvd: 2
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 1638400 kB
Noter que le champ shared
est presque nul, la mémoire partagée
étant classée dans la mémoire utilisée.
Avec des pages dynamiques¶
On suppose une machine de 6 Go de RAM, 1,5 Go de shared buffers et ces paramètres :
PostgreSQL arrêté, aucune page n’est réservée ou allouée :
$ sudo grep Huge /proc/meminfo
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 0 kB
$ sudo grep Huge /proc/meminfo
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 780
HugePages_Free: 751
HugePages_Rsvd: 751
HugePages_Surp: 780
Hugepagesize: 2048 kB
Hugetlb: 1597440 kB
max_connections
de 100 à 800 et redémarrage de PostgreSQL,
le nombre de pages réservées est plus élevé :
$ sudo grep Huge /proc/meminfo
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 794
HugePages_Free: 755
HugePages_Rsvd: 755
HugePages_Surp: 794
Hugepagesize: 2048 kB
Hugetlb: 1626112 kB
Une fois les shared buffers pleins (pg_prewarm
permet d’accélérer cela au besoin), il n’y a presque plus de pages libres :
sudo grep Huge /proc/meminfo
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 794
HugePages_Free: 6
HugePages_Rsvd: 6
HugePages_Surp: 794
Hugepagesize: 2048 kB
Hugetlb: 1626112 kB
Par processus¶
Dans /proc/<PID>/status
, la ligne HugetlbPages
indique
la quantité de RAM en huge pages que le processus
utilise (forcément de la mémoire partagée ici).
Exemple de valeurs HugetlbPages dans /proc/PID/status
On constate que seuls certains processus clients ont « touché » aux 1,5 Go de mémoire partagée.
for p in $(pgrep postgres) ; do cat /proc/$p/cmdline ; printf '\n'; grep HugetlbPages /proc/$p/status ; done
/usr/lib/postgresql/15/bin/postgres-D/var/lib/postgresql/15/main-cconfig_file=/etc/postgresql/15/main/postgresql.conf
HugetlbPages: 59392 kB
postgres: 15/main: checkpointer
HugetlbPages: 38912 kB
postgres: 15/main: background writer
HugetlbPages: 1253376 kB
postgres: 15/main: walwriter
HugetlbPages: 24576 kB
postgres: 15/main: autovacuum launcher
HugetlbPages: 43008 kB
postgres: 15/main: logical replication launcher
HugetlbPages: 12288 kB
postgres: 15/main: postgres postgres [local] idle
HugetlbPages: 1581056 kB <------ ce processus client avait exécuté un pg_prewarm sur toute la base
postgres: 15/main: autovacuum worker postgres
HugetlbPages: 1574912 kB <------ autovacuum a parcouru tous les shared buffers
Si la ligne HugetlbPages
n’est pas présente dans le fichier status
,
cette commande calcule le volume de Huge pages utilisé (en Mo) :
sudo grep -B 11 'KernelPageSize: 2048 kB' /proc/$(sudo head -1 $PGDATA/postmaster.pid)/smaps | grep "^Size:" | awk 'BEGIN{sum=0}{sum+=$2}END{print sum/1024}'
1560
Gains de performance¶
Les huge pages apportent un gain en performances pures mesurable mais modeste (quelques pour cent dans les configurations avec beaucoup de RAM et de clients). Les accès aux TLB sont en effet déjà très optimisés.
Le support Dalibo a cependant déjà vu des impacts plus notables, comme décrit dans cet article de Julien Rouhaud : Diagnostique de lenteurs inattendues.
L’article montre aussi un gain sur la taille de la PTE (la table de pagination) et ce sur chaque processus.
La somme des taille des tables de pagination se voit dans /proc/meminfo
:
Si la session dure et parcoure tout le cache de PostgreSQL, la PTE peut monter à 16 Mo par processus (avec des pages normales de 4 ko pour une mémoire partagée de 8 Go). Avec des huge pages de 2 Mo, la PTE tombe à environ 100 ko par processus. Avec des centaines ou milliers de sessions et sans pooling, l’économie de mémoire justifie l’utilisation des huge pages.
Comparaison des PTE d’un processus avec des pages de 4 ko ou 2 Mo
Sur un processus donné, la PTE se trouve ainsi :
Voici un processus ayant accédé aux 8 Go de shared buffers, d’abord avec des pages de 4 ko :
$ sudo cat /proc/11472/status
Name: postgres
Umask: 0077
State: R (running)
Tgid: 11472
Ngid: 0
Pid: 11472
PPid: 9059
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
FDSize: 128
Groups: 7 50 90 91 92 93 96 100 1000 1001
NStgid: 11472
NSpid: 11472
NSpgid: 11472
NSsid: 11472
VmPeak: 8449256 kB
VmSize: 8448776 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 8352340 kB
VmRSS: 8352340 kB
RssAnon: 8532 kB
RssFile: 7752 kB
RssShmem: 8336056 kB <-- 8 Go de shared buffers: shared memory « normale »
VmData: 9188 kB
VmStk: 136 kB
VmExe: 6204 kB
VmLib: 7376 kB
VmPTE: 16420 kB <-------- 1 Mo de RAM utilisée, juste par la table de pagination
VmPMD: 48 kB
VmSwap: 0 kB
HugetlbPages: 0 kB
Threads: 1
SigQ: 0/63677
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000001301800
SigCgt: 0000000184006287
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
Seccomp: 0
Cpus_allowed: f
Cpus_allowed_list: 0-3
Mems_allowed: 00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 4786
nonvoluntary_ctxt_switches: 11157
La même, avec des pages de 2 Mo :
cat /proc/11957/status
Name: postgres
Umask: 0077
State: R (running)
Tgid: 11957
Ngid: 0
Pid: 11957
PPid: 11934
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
FDSize: 64
Groups: 7 50 90 91 92 93 96 100 1000 1001
NStgid: 11957
NSpid: 11957
NSpgid: 11957
NSsid: 11957
VmPeak: 8449808 kB
VmSize: 8449800 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 14908 kB
VmRSS: 14908 kB
RssAnon: 7868 kB
RssFile: 7040 kB
RssShmem: 0 kB
VmData: 8188 kB
VmStk: 136 kB
VmExe: 6204 kB
VmLib: 7376 kB
VmPTE: 92 kB <---------- 92 ko de pages utilisées par la PTE
VmPMD: 44 kB
VmSwap: 0 kB
HugetlbPages: 8495104 kB <-- 8 Go de shared buffers, en huge pages cette fois-ci
Threads: 1
SigQ: 0/63677
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000001301800
SigCgt: 0000000184006287
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
Seccomp: 0
Cpus_allowed: f
Cpus_allowed_list: 0-3
Mems_allowed: 00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 5091
Impact sur les limites d’allocation de la mémoire¶
Le paramétrage de l’overcommit est fortement conseillé sur un serveur PostgreSQL pour sa stabilité.
L’utilisation de huge pages complexifie le calcul de
vm.overcommit_ratio
ou vm.overcommit_kbytes
:
les huge pages doivent être déduites de la mémoire « disponible »
pour le calcul du CommitLimit
. Voir le détail sur
la page dédiée au calcul de l’overcommit avec les huge pages,
et les exemples.
Aller plus loin¶
- Translation lookaside buffer (Wikipédia)
- Hugepages (wiki.debian.org)
- Huge pages and databases: Working with abundant memory in modern servers (Fernando Laudares Camargos, FOSDEM 2019), avec tests de performances
-
Documentation de Linux, essentiellement sur kernel.org :
-
Documentation de PostgreSQL : Linux memory overcommit
-
Diagnostique de lenteurs inattendues (Julien Rouhaud), sur un exemple d’impact sur les performances.
-
voir notamment le dernier paragraphe de la documentation du paramètre huge_page. ↩
-
à partir de cette version, il est possible de positionner
/sys/kernel/mm/transparent_hugepage/defrag
à la valeurdefer
oumadvise+defer
, ce qui supprime théoriquement le plus gros des problèmes de latences qui avaient été observées jusqu’alors. Il reste toutefois possiblement des problèmes liés aux mécanismes de maintenance de ces huge pages lorsque la pression mémoire est importante, notamment la refragmentation de celles-ci en cas de swapping. ↩