MySQL – Linux – Ubuntu Server – Utiliser les HugePages


Préambule

Lorsque l’on a besoin d’allouer de grandes quantités mémoire pour MySQL, il n’est pas possible de rester en mode d’adressage mémoire conventionnel. En effet, le nombre de pointeurs vers le buffer cache InnoDB se multiplie et le noyau MySQL passe son temps à gérer le buffer cache InnoDB plutôt que de traiter les requêtes.

La solution consiste à passer le noyau Linux en mode « HugePages » afin de pouvoir côté MySQL utiliser les « large pages ».

Cet article se focalise particulièrement sur la distribution Ubuntu. Toutefois il peut être utilisé pour les autres distributions plus simples à passer dans ce mode de fonctionnement du noyau. La différence essentielle réside dans la gestion de la sécurité de Ubuntu Server avec AppArmor, là où les autres distributions utilisent directement SELinux. Par ailleurs, la mise à jour de Grub n’est pas obligatoire pour les autres distributions. Il est donc très aisé de configurer n’importe quelle autre distribution Linux en partant de cet article.

Préparation du noyau Linux

Installer SELinux

Avant de commencer l’installation de SELinux, il faut arrêter AppArmor :

service apparmor stop

L’installation de SELinux se fait comme suit :

apt-get install selinux
reboot

L’installation de SELinux procède dans le même temps à la désinstallation de AppArmor.

Configurer SELinux /etc/selinux/config

De base, SELinux est correctement configuré :

SELINUX=permissive
SELINUXTYPE=default

Préparation des HugePages (spécifique Ubuntu)

Edition de /etc/default/grub

# Toujours utiliser les HugePages
GRUB_CMDLINE_LINUX="transparent_hugepage=always"
# Pour 4 Go
GRUB_CMDLINE_LINUX="hugepages=1024"

Application des modifications et redémarrage du serveur

update-grub
reboot

Création d’un fichier /etc/init.d/local

#!/bin/bash
#
### BEGIN INIT INFO
# Provides: local
# Required-Start:
# Required-Stop:
# Should-Start:
# Should-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Kernel parameters modification
# Description: Kernel paramerts modification
### END INIT INFO
#

echo always > /sys/kernel/mm/transparent_hugepage/enabled

Rendre /etc/init.d/local exécutable

chmod +x /etc/init.d/local

Déploiement du script pour les différents niveaux d’exécution

update-rc.d local defaults 80

Préparation des HugePages (toutes distributions)

Préparation de /dev/shm

Le device spécial /dev/shm doit être préparé au niveau du fichier /etc/fstab. Dans le cas présent, il est préparé pour un partage mémoire de 4 Go :

shmfs /dev/shm tmpfs defaults,size=4G 0 0

Le device doit être démonté et remonté pour la prise en compte de la nouvelle configuration :

mount -o remount /dev/shm

Un contrôle de la bonne prise en compte de la nouvelle configuration est possible ainsi :

df -h|grep shm

tmpfs 4.0G 0 4.0G 0% /dev/shm

Création du fichier /etc/sysctl.d/10-nr-hugepages.conf

# Valeurs pour 4 Go de RAM et avec le groupe mysql de numéro 113
# Exécuter "id mysql" pour connaître le numéro du groupe
vm.nr_hugepages=1024
transparent_hugepage=always
vm.hugetlb_shm_group=113

Rechargement de sysctl

sysctl -p

Vérification de la mémoire HugePages

cat /proc/meminfo|grep Huge

AnonHugePages: 169984 kB
ShmemHugePages: 0 kB
HugePages_Total: 1024
HugePages_Free: 1024
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB

Le système indique qu’il est capable d’allouer ici 2 Go de HugePages (1024 x 2048 kB) HugePages_Total x Hugepagesize

Configuration de l’instance MySQL

Edition du fichier /etc/mysql/mysql.conf.d/<fichier>.cnf

Il suffit d’ajouter dans la rubrique [mysqld] la valeur « large-pages » telle quelle :

[mysqld]
...
large-pages

Exemple de configuration de MySQL avec 1,5 Go d’InnoDB Buffer en 32 segments

innodb_buffer_pool_size = 1536M
innodb_buffer_pool_instances = 32

Finalisation de la configuration

Boot sanitaire du système

Le boot sanitaire a pour vocation de contrôler que l’ensemble de la configuration est persistante et stable :

reboot

Contrôle HugePages

A ce stade un contrôle de /proc/meminfo|grep Huge doit donner ce résultat :

cat /proc/meminfo|grep Huge

AnonHugePages: 251904 kB
ShmemHugePages: 0 kB
HugePages_Total: 1024
HugePages_Free: 989
HugePages_Rsvd: 772
HugePages_Surp: 0
Hugepagesize: 2048 kB

Comme nous pouvons le voir, 772 pages de 2 Mo ont été réservées par MySQL, soit 1544 Mo.

Contrôle segments de Shared Memory

Un contrôle de l’utilisation précise de la Shared Memory peut être réalisé ainsi :

ipcs -m

------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 0 root 644 80 2
0x00000000 32769 root 644 16384 2
0x00000000 65538 root 644 280 2
0x00000000 131075 mysql 600 14680064 1 dest
0x00000000 163844 mysql 600 52428800 1 dest
0x00000000 196613 mysql 600 52428800 1 dest
0x00000000 229382 mysql 600 52428800 1 dest
0x00000000 262151 mysql 600 52428800 1 dest
0x00000000 294920 mysql 600 52428800 1 dest
0x00000000 327689 mysql 600 52428800 1 dest
0x00000000 360458 mysql 600 52428800 1 dest
0x00000000 393227 mysql 600 52428800 1 dest
0x00000000 425996 mysql 600 52428800 1 dest
0x00000000 458765 mysql 600 52428800 1 dest
0x00000000 491534 mysql 600 52428800 1 dest
0x00000000 524303 mysql 600 52428800 1 dest
0x00000000 557072 mysql 600 52428800 1 dest
0x00000000 589841 mysql 600 52428800 1 dest
0x00000000 622610 mysql 600 52428800 1 dest
0x00000000 655379 mysql 600 52428800 1 dest
0x00000000 688148 mysql 600 52428800 1 dest
0x00000000 720917 mysql 600 52428800 1 dest
0x00000000 753686 mysql 600 52428800 1 dest
0x00000000 786455 mysql 600 52428800 1 dest
0x00000000 819224 mysql 600 52428800 1 dest
0x00000000 851993 mysql 600 52428800 1 dest
0x00000000 884762 mysql 600 52428800 1 dest
0x00000000 917531 mysql 600 52428800 1 dest
0x00000000 950300 mysql 600 52428800 1 dest
0x00000000 983069 mysql 600 52428800 1 dest
0x00000000 1015838 mysql 600 52428800 1 dest
0x00000000 1048607 mysql 600 52428800 1 dest
0x00000000 1081376 mysql 600 52428800 1 dest
0x00000000 1114145 mysql 600 52428800 1 dest
0x00000000 1146914 mysql 600 52428800 1 dest
0x00000000 1179683 mysql 600 52428800 1 dest

On peut voir que MySQL a alloué 31 segments de mémoire partagée de 50 Mo chacun et un segment de 14 Mo, soit 1564 Mo.

Contrôle de l’allocation mémoire dans MySQL

En tant que root, exécuter la requête suivante :

mysql> SELECT * FROM information_schema.INNODB_BUFFER_POOL_STATS \G

*************************** 1. row ***************************
POOL_ID: 0
POOL_SIZE: 3125
FREE_BUFFERS: 3094
DATABASE_PAGES: 31
OLD_DATABASE_PAGES: 0
MODIFIED_DATABASE_PAGES: 0
PENDING_DECOMPRESS: 0
PENDING_READS: 0
PENDING_FLUSH_LRU: 0
PENDING_FLUSH_LIST: 0
PAGES_MADE_YOUNG: 0
PAGES_NOT_MADE_YOUNG: 0
PAGES_MADE_YOUNG_RATE: 0
PAGES_MADE_NOT_YOUNG_RATE: 0
NUMBER_PAGES_READ: 31
NUMBER_PAGES_CREATED: 0
NUMBER_PAGES_WRITTEN: 2
PAGES_READ_RATE: 0
PAGES_CREATE_RATE: 0
PAGES_WRITTEN_RATE: 0
NUMBER_PAGES_GET: 383
HIT_RATE: 0
YOUNG_MAKE_PER_THOUSAND_GETS: 0
NOT_YOUNG_MAKE_PER_THOUSAND_GETS: 0
NUMBER_PAGES_READ_AHEAD: 0
NUMBER_READ_AHEAD_EVICTED: 0
READ_AHEAD_RATE: 0
READ_AHEAD_EVICTED_RATE: 0
LRU_IO_TOTAL: 0
LRU_IO_CURRENT: 0
UNCOMPRESS_TOTAL: 0
UNCOMPRESS_CURRENT: 0
*************************** 2. row ***************************
POOL_ID: 1
POOL_SIZE: 3125
FREE_BUFFERS: 3125
DATABASE_PAGES: 0
OLD_DATABASE_PAGES: 0
MODIFIED_DATABASE_PAGES: 0
PENDING_DECOMPRESS: 0
PENDING_READS: 0
PENDING_FLUSH_LRU: 0
PENDING_FLUSH_LIST: 0
PAGES_MADE_YOUNG: 0
PAGES_NOT_MADE_YOUNG: 0
PAGES_MADE_YOUNG_RATE: 0
PAGES_MADE_NOT_YOUNG_RATE: 0
NUMBER_PAGES_READ: 0
NUMBER_PAGES_CREATED: 0
NUMBER_PAGES_WRITTEN: 0
PAGES_READ_RATE: 0
PAGES_CREATE_RATE: 0
PAGES_WRITTEN_RATE: 0
NUMBER_PAGES_GET: 0
HIT_RATE: 0
YOUNG_MAKE_PER_THOUSAND_GETS: 0
NOT_YOUNG_MAKE_PER_THOUSAND_GETS: 0
NUMBER_PAGES_READ_AHEAD: 0
NUMBER_READ_AHEAD_EVICTED: 0
READ_AHEAD_RATE: 0
READ_AHEAD_EVICTED_RATE: 0
LRU_IO_TOTAL: 0
LRU_IO_CURRENT: 0
UNCOMPRESS_TOTAL: 0
UNCOMPRESS_CURRENT: 0
*************************** 3. row ***************************
POOL_ID: 2
POOL_SIZE: 3125
FREE_BUFFERS: 3122
DATABASE_PAGES: 3

[...]

*************************** 32. row ***************************
POOL_ID: 31
POOL_SIZE: 3125
FREE_BUFFERS: 3125
DATABASE_PAGES: 0
OLD_DATABASE_PAGES: 0
MODIFIED_DATABASE_PAGES: 0
PENDING_DECOMPRESS: 0
PENDING_READS: 0
PENDING_FLUSH_LRU: 0
PENDING_FLUSH_LIST: 0
PAGES_MADE_YOUNG: 0
PAGES_NOT_MADE_YOUNG: 0
PAGES_MADE_YOUNG_RATE: 0
PAGES_MADE_NOT_YOUNG_RATE: 0
NUMBER_PAGES_READ: 0
NUMBER_PAGES_CREATED: 0
NUMBER_PAGES_WRITTEN: 0
PAGES_READ_RATE: 0
PAGES_CREATE_RATE: 0
PAGES_WRITTEN_RATE: 0
NUMBER_PAGES_GET: 0
HIT_RATE: 0
YOUNG_MAKE_PER_THOUSAND_GETS: 0
NOT_YOUNG_MAKE_PER_THOUSAND_GETS: 0
NUMBER_PAGES_READ_AHEAD: 0
NUMBER_READ_AHEAD_EVICTED: 0
READ_AHEAD_RATE: 0
READ_AHEAD_EVICTED_RATE: 0
LRU_IO_TOTAL: 0
LRU_IO_CURRENT: 0
UNCOMPRESS_TOTAL: 0
UNCOMPRESS_CURRENT: 0
32 rows in set (0.00 sec)