Optimiser les performances des disques dur sur Linux
Contents
- 1 Introduction
- 2 Quels sont les facteurs de lenteur ?
- 3 Alignement des partitions
- 4 Différences entre disques électromécanique et SSD
- 5 Les différents bus de données
- 6 Caches et taux de transfert
- 7 Requêtes IO et caches
- 8 Les accès en lecture séquentiel
- 9 Les scheduleurs
- 10 Optimisations pour les SSDs
- 11 References
Software version | Kernel 2.6.32+ |
---|---|
Operating System | Red Hat 6.3 Debian 7 |
Website | Kernel Website |
Last Update | 02/01/2014 |
Others |
1 Introduction
Les disques dur physiques sont aujourd'hui ce qu'il y a de plus lent dans nos machines. Que ce soit les disques dur physique à plateaux ou bien même les SSD ! Mais il y a moyen d'optimiser selon les besoins des performances de ceux ci. Nous allons voir ici plusieurs aspects qui devrait vous aider à comprendre pourquoi il peut y avoir des saturations, comment les éviter et les solutions pour faire des benchmarks.
2 Quels sont les facteurs de lenteur ?
Il existe plusieurs facteurs qui peuvent être à la cause de lenteurs sur vos disques (IO disques). Si ce sont des disques électromécanique, ils seront plus lent que des SSD et auront des contraintes supplémentaire :
- La vitesse de rotation des disques
- La vitesse de lecture par seconde sera meilleure sur la fin des disques (la partie la plus éloignée du centre)
- Les données qui ne sont pas alignées sur le disque
- Les petites partitions présentes sur la fin du disque
- La vitesse du bus sur lesquels les disques sont
- Le seek time, qui correspond au temps de déplacement de la tête de lecture
3 Alignement des partitions
L'alignement consiste à faire correspondre les blocs logiques des partitions avec les blocs physiques afin de limiter les opérations de lecture/écriture et ainsi ne pas entraver les performances.
Les SSD actuels travaillent en interne sur des blocs de 1 ou 2 Mio, c'est à dire respectivement 1 048 576 ou 2 097 152 octets. Considérant qu'un secteur stocke 512 octets, il faudra ainsi 2 048 secteurs pour stocker 1 048 576 octets.
Si, traditionnellement, les systèmes d'exploitation faisaient démarrer la première partition au 63ème secteur, les dernières versions prennent en compte les contraintes des SSD.
Ainsi Parted peut aligner automatiquement les début des partitions sur des multiples de 2 048 secteurs.
Pour vous assurer du bon alignement des partitions, entrez la commande suivante avec les privilèges d'administration et vérifiez que le nombre de secteurs au début de chacune de vos partitions est bien un multiple de 2 048. Voici la commande pour une table de partition de type MSDOS :
fdisk |
fdisk -lu /dev/sdX |
Dans le cas ou vous avez une table de partition de type GPT :
parted |
parted -l /dev/sdX |
4 Différences entre disques électromécanique et SSD
Maintenant, pour mieux comprendre la différence entre la fin du disque (celui le plus proche du centre) et le début disque (le plus éloigné du centre), laissez moi vous montrer un test entre une clef USB (linéaire, équivalent à un SSD) et un disque dur (non linéaire). Pour cela nous allons utiliser un outil de benchmark appelé bonnie++. Nous l'installons donc :
aptitude |
aptitude install bonnie++ |
Et nous allons lancer une capture sur les 2 disques avec l'utilitaire zcav qui nous permet de tester le débit en mode raw :
zcav |
for i in sdc sdd ; do zcav -c1 /dev/$i >> ~/$i.zcav done |
L'option -c permet de dire le nombre de fois qu'il faut lire le disque entièrement.
Puis nous allons générer un graphique des données avec Gnulpot :
Pour rappel, j'ai fais un article sur Gnuplot. Voici le résultat :
On voit très clairement que le disque dur se comporte très bien au début et souffre sur l'intérieur. La vitesse est quasiment 2 fois plus importante à l'extérieur à qu'à l'intérieur et cela s'explique par le bras oscillant qui lit plus de données sur une même période de temps à l'extérieur.
5 Les différents bus de données
Il existe différents types de bus :
- PCI
- PCI-X
- PCIe
- AGP
- ...
Aujourd'hui c'est le PCI-X qui est le plus rapide. Si vous avez des cartes RAID, vous pouvez vérifiez la vitesse de l'horloge, afin de s'assurer qu'elle tourne bien à son maximum pour délivrer autant de débit que possible. Vous pouvez trouver sur wikipedia toutes les informations nécessaires pour ces bus. L'important est de vérifier :
- La taille du bus : 32/64 bits
- La vitesse de l'horloge
Voici un petit récapitulatif approximatif (varie avec les avancées technologiques) :
Objet | Latence | Débit |
---|---|---|
Disque 10krpm | 3ms | 50Mo/s |
Accès au swap | 8ms | 50Mo/s |
Disque SSD | 0.5ms | 100Mo/s |
Ethernet gigabit | 1ms | 133Mo/s |
Interface pci | 0.1us | 133Mo/s |
malloc/mmap | 0.1us | |
fork | 0.1ms | |
gettimeofday | 1us | |
context switch | 3us | |
RAM | 80ns | 8Go/s |
Interface pci-express 16x | 10ns | 8Go/s |
Cache L2 | 5ns | |
Cache L1 | 1ns | |
Cache L1 | 0.3ns | 40Go/s |
Il existe également les bus de type SCSI. Ceux là sont un peu particulier, mais il faut faire attention à ne pas mixer différentes vitesses d'horloge sur le même bus, la taille des bus, les terminaisons passives/actives...
Vous pouvez utiliser la commande sginfo pour récupérer tous les paramètres scsi de vos devices :
aptitude install sg-utils sginfo -a /dev/sda |
6 Caches et taux de transfert
Les contrôleurs de disque récents ont des caches intégrés pour accélérer les accès en lecture et écriture. Par défaut, beaucoup de constructeurs désactivent le cache en écriture pour éviter toute corruption de données. Cependant, il est possible de configurer ce cache pour accélérer grandement les accès. De plus, lorsque ces contrôleurs embarquent avec une batterie, les cartes sont capables de garder pendant quelques heures à quelques jours les données. Une fois la machine allumée, la carte se chargera d'écrire les données sur le ou les disques.
Pour calculer le taux de transfert d'un disque en bytes/secondes :
taux = (secteurs par pistes * rpm * 512) / 60
Pour les disques utilisant les ZCAV, il faut remplacer les secteurs par pistes par la moyenne de bytes par pistes :
vitesse = ((moyenne de secteurs par pistes * 512 * rpm) /60) / 1000000
7 Requêtes IO et caches
Les requêtes IO de haut niveau comme les lecture/écriture faites par la couche Linux Virtual Filesystem doit être transformée en requêtes de block device. Le kernel procède alors a la mise en queue de chaque block device. Chaque block physique effectue sa propre demande de mise en queue. Les requêtes mises en queue sont des "Requests Descriptor". Elles décrivent les structures de données dont le kernel a besoin pour s'occuper des requêtes IO. Une "request descriptor" peut pointer vers un transfert IO qui pointera à son tour vers plusieurs blocks disques.
Lorsqu'une demande d'IO sur un device est émise, une requête spéciale de structure est mise en queue dans la "Request Queue" pour le device en question. La structure de la requête contient des pointeurs désignant les secteurs sur le disque ou le "Buffer Cache (Page Cache)". Si la demande de requête est :
- pour lire des données, le transfert se fera du disque vers la mémoire.
- pour l'écriture de données, le transfert se fera de la mémoire vers le disque.
L'ordonnancement des requêtes IO est un effort commun. Un drivers de haut niveau place une requête IO dans la request queue. Cette requête est envoyée au scheduler qui va utiliser un algorithme pour la traiter. Pour éviter tout effet d'étranglement, la requête ne sera pas immédiatement traitée, mais passée en mode bloquée ou connectée. Une fois qu'un certain nombre de requêtes sera atteint, la queue sera déconnectée et un driver de bas niveau se chargera des transferts de requêtes IO pour déplacer le blocks (disque) et pages (mémoire).
L'unité utilisé pour le transfert d'IO est une page. Chaque page transférée depuis le disque correspond à une page en mémoire. Vous pouvez connaitre la taille des pages cache et buffer cache comme ceci :
cat |
> grep -ie '^cache' -ie '^buffer' /proc/meminfo Buffers: 233184 kB Cached: 2035636 kB |
- Buffers : utilisé pour le stockage des metadatas du filesystem
- Cached : utilisé pour cacher des fichiers de donnée
En User mode, les programmes n'ont pas accès au contenu des buffers directement. Les buffers sont gérées par le kernel dans le "kernel space". Le kernel doit copier les données de ces buffers, dans l'espace user mode du processus qui a fait la demande de requête de fichier/inode représenté par des blocks cache ou pages mémoire.
8 Les accès en lecture séquentiel
Notes |
L'utilisation de la technologie read-ahead (lecture anticipée) n'a de sens que pour les applications qui lisent séquentiellement les données ! Aucuns intérêts pour les accès aléatoire. |
Lorsque vous faites des accès disques, le kernel essaye de lire séquentiellement les données sur le disque. La lecture anticipée (Read ahead), permet de lire plus de block que ce qui est demandé pour anticiper la demande et stocker ces données en cache. Car lorsqu'un block est lu, il est plus que très fréquent de devoir lire le bloc d'après, c'est pourquoi il peut être intéressant de tuner le read-ahead. Les avantages de cette méthode sont que :
- Le kernel est capable de répondre plus rapidement à la demande
- La charge du contrôleur de disques est moins chargé
- Les temps de réponse sont grandement améliorés
Notes |
L'algorithme est conçu pour s'arrêter de lui même s'il détecte trop d'accès aléatoire pour ne pas casser les performances. N'ayez donc pas peur de tester cette fonctionnalité. |
L'algorithme read-ahead est gérer par 2 valeurs :
- La fenêtre courante : elle contrôle le nombre de données que le kernel va devoir traiter lorsqu'il fera des accès IO.
- La fenetre ahead
Lorsqu'une application fait une demande d'accès de pages en lecture dans le cache buffer (qui font partie de la fenêtre courante), les IO se font sur la fenêtre ahead ! Par contre, quant l'application a fini sa lecture sur la fenêtre courante, la fenêtre ahead devient la nouvelle fenêtre courante et une nouvelle ahead est créer.
Si l'accès à une page se fait dans la fenêtre courante, la taille de la nouvelle fenêtre ahead sera alors augmentée de 2 pages. Si le débit de lecture ahead est faible, la taille de la fenêtre ahead sera réduite petit à petit.
Pour connaitre la taille des read ahead en secteurs (1 secteur = 512 Bytes) :
blockdev |
> blockdev --getra /dev/sda 256 |
Ou bien en kilobyte :
cat |
> cat /sys/block/sda/queue/read_ahead_kb 128 |
Si vous souhaitez bencher pour voir les meilleurs performances que vous pouvez atteindre avec vos disques :
Il faut cependant prendre ces informations avec des pincettes puisqu'il faudrait les tester avec l'applicatif que vous voulez faire tourner sur ce disque pour obtenir un résultat vraiment satisfaisant. Ecrasez donc la valeur dans le /sys pour la changer. Insérez là dans /etc/rc.local pour la rendre persistante.
Notes |
La fenêtre read ahead initiale est égale à la moitié de celle paramétrée. Celle paramétrée correspond à la taille maximale de la fenêtre read ahead ! |
Vous pouvez obtenir un rapport comme ceci :
blockdev |
> blockdev --report /dev/sda RO RA SSZ BSZ StartSec Size Device rw 256 512 1024 0 250000000000 /dev/sda |
9 Les scheduleurs
Lorsque que le kernel reçoit des demandes d'IO multiples en simultané, il doit les gérer pour éviter des conflits. La meilleure solution (au point de vue performances) d'accès au disque est la séquentialité des données adressées en block logique. De plus, les demandes d'IO sont priorisé par rapport à leur taille. Plus elles sont petites, plus elles vont être placées en haut de la queue, puisque le disque arrivera rapidement à délivrer ce type de données bien plus rapidement que pour des grosses.
Afin d'éviter les goulets d'étranglement, le kernel s'assure que tous les process récupèrent tous des IO. C'est au rôle du scheduleur de s'assurer que les IO en bas de la queue soient processées et non toujours remis à plus tard.
Lorsque l'on ajoute une entrée dans la queue, le kernel va d'abord tenter d'élargir la queue en cours et insérer la nouvelle requête dedans. Si ce n'est pas possible, la nouvelle requête sera assignée à une autre queue qui utilise un algorithme "elevator".
Pour déterminer quel est l'IO scheduleur (algo elevator) :
grep |
> grep CONFIG_DEFAULT_IOSCHED /boot/config-`uname -r` CONFIG_DEFAULT_IOSCHED="cfq" |
Voici les scheduleurs que vous pouvez trouver :
- deadline : moins d'efficacité, mais moins de temps de réponse
- anticipatory : des temps d'attente plus long, mais une meilleure efficacité
- noop : le plus simple, il est fait pour économiser le CPU
- cfq : essaye d'être le plus homogène possible en tous points
Pour plus d'informations officielles : http://www.kernel.org/doc/Documentation/block/
Pour connaitre le scheduleur actuel utilisé :
cat |
> cat /sys/block/sda/queue/scheduler noop deadline [cfq] |
C'est donc l'algorithme entre crochet qui est utilisé. Pour changer de scheduleur :
echo |
> echo noop > /sys/block/sda/queue/scheduler [noop] deadline cfq |
N'oubliez pas de mettre cette ligne dans /etc.rc.local si vous voulez du persistant.
WARNING |
Ne faites pas ce qui suit sur une machine en production ou vous risquez d'avoir pendant quelques secondes de gros ralentissements |
Pour écrire toutes les données en cache sur le disque, vider ces derniers et s'assurer de l'utilisation du dernier algorithme choisi :
sync sysctl -w vm.drop_caches=3 |
9.1 cfq
C'est le scheduleur par défaut sur Linux, qui veut dire : Completly Fair Queuing. Ce scheduleur garde 64 queues de requêtes, les IO sont adressées via l'algorithme Round Robin à ces queues. Les requêtes adressées sont utilisées pour limiter au maximum des déplacements des têtes de lecture et donc gagner en vitesse.
Les options possible sont :
- quantum : le nombre total de requêtes placées sur la queue de dispatch par cycle
- queued : le nombre maximum de requêtes autorisées par queue
Pour tuner un peu l'élévator CFQ, il nous faut ce package installé pour avoir la commande ionice :
aptitude |
aptitude install util-linux |
ionice permet de changer la priorité en lecture/écriture sur un process. Voici un exemple :
ionice |
ionice -p1000 -c2 -n7 |
- -p1 : active la demande sur le PID 1000
- -c2 : permet de spécifier la classe souhaitée :
- 0 : aucune
- 1 : temps réel
- 2 : best-effort
- 3 : idle
- -n7 : permet de spécifier la priorité sur la commande/pid choisis entre 0 (le plus important) et 7 (le moins important)
9.2 deadline
A chaque requête du scheduleur, est assigné une date d'expiration. Lorsque ce temps est passé, le scheduleur déplace cette requête sur le disque. Pour éviter une trop grosse sollicitation de déplacements, le deadline scheduleur va également s'occuper de traiter les autres requêtes vers un nouvel emplacement sur le disque.
Il est possible de tuner certains paramètres te que :
- read_expire : nombre de millisecondes avant que chaque requêtes de lecture d'IO expirent
- write_expire : nombre de millisecondes avant que chaque requêtes d'écritures d'IO expirent
- fifo_batch : le nombre de requêtes à déplacer de la liste du scheduleur vers la queue de 'block device'
- writes_starved : permet de paramétrer la préférence sur de fois que le scheduleur doit faire des lectures avant de faire des écritures. Une fois le nombre de lectures atteinte, les données seront déplacées dans la queue de 'block device' et les écritures seront traitées.
- front_merge : une fusion (ajout) des requêtes en bas de la queue est la façons normale dont les requêtes sont traitées pour être insérées dans la queue. Après un writes_starved, les requêtes tentent d'être ajoutées au début de la queue. Pour désactiver cette fonctionnalité, mettez là à 0.
Voici quelques exemples d'optimisations que j'ai trouvé pour DRBD qui utilise le scheduleur deadline :
- Disable front merges:
echo |
echo 0 > /sys/block/<device>/queue/iosched/front_merges |
- Reduce read I/O deadline to 150 milliseconds (the default is 500ms):
echo |
echo 150 > /sys/block/<device>/queue/iosched/read_expire |
- Reduce write I/O deadline to 1500 milliseconds (the default is 3000ms):
echo |
echo 1500 > /sys/block/<device>/queue/iosched/write_expire |
9.3 anticipatory
Dans bien des situations, une application qui lit des blocks, attends et reprends, va lire les blocks qui suivent les blocks vennant d'être lu. Mais si les données souhaitées ne se trouvent pas dans les blocks suivant le dernier lu, il y aura de la latence supplémentaire. Pour éviter ce genre de désagréments, le scheduleur anticipatory va répondre à ce besoin en essayant de trouver les blocks qui vont être demandés et les mettre en cache. Le gain de performance peut alors être grandement amélioré.
Les requêtes d'accès en lecture et écritures sont procédés par batchs. Chaque batch correspond en fait à un temps de réponse groupé.
Voici les options :
- read_expire : nombre de millisecondes avant que chaque requêtes de lecture d'IO expirent
- write_expire : nombre de millisecondes avant que chaque requêtes d'écritures d'IO expirent
- antic_expire : combien de temps faut il attendre pour une autre requête avant d'aller lire la suivante
9.4 noop
L'option noop permet de ne pas utiliser d'algorithme intelligent. Il sert les requêtes au fur et à mesure de leur demande. C'est notamment utiliser pour les machines hosts en virtualisation. Ou bien les disques qui embarquent la technologie TCQ afin d'éviter que 2 algorithmes se chevauchent et fasse perdre des performances au lieu d'en gagner.
10 Optimisations pour les SSDs
Vous avez maintenant compris l'importance des options et les différences entre les disques comme expliqué ci dessus. Pour les SSD, il y a un peu de tuning à faire si l'on souhaites avoir les meilleures performances, tout en optimisant la durée de vie de ceux ci.
10.1 Alignement
Une des premières choses à faire est de créer ses partitions correctement alignées. Voici un exemple de création de partitions alignées :
datas_device=/dev/sdb parted -s -a optimal $datas_device mklabel gpt parted -s -a optimal $datas_device mkpart primary ext4 0% 100% parted -s $datas_device set 1 lvm on |
- ligne 1 : on créer un label de type gpt pour les grosses partitions (supérieures à 2Tb)
- ligne 2 : on créer une partition qui prends la totalité du disque
- ligne 3 : on lui indique que cette partition sera de type LVM
10.2 TRIM
La fonction TRIM, est désactivée par défaut. Il vous faudra également un noyau au minimum égale à 2.6.33. Afin d'utiliser le TRIM, il vous faudra utiliser un des filesystems étudiés pour les SSD supportant cette technologie :
- Btrfs
- Ext4
- XFS
- JFS
Dans votre fstab, il faudra ajouter ensuite l'option 'discard' pour activer le fameux TRIM :
10.2.1 Sur LVM
Il est également possible d'activer le TRIM sur LVM :
10.3 noatime
Il est possible de désactiver les temps d'accès sur les fichiers. C'est à dire que par défaut, à chaque fois que vous faites un accès à un fichier, il est enregistré la date d'accès sur ce dernier. S'il y a beaucoup d'accès concurrent sur une partition, ça finit par se sentir énormément. C'est pourquoi vous pouvez le désactiver si cette fonctionnalité ne vous est pas utile. Dans votre fstab, rajoutez l'option noatime :
/etc/fstab |
/dev/mapper/vg-home /home ext4 defaults,noatime 0 2 |
Il est possible d'utiliser la même fonctionnalité pour les dossiers :
/etc/fstab |
/dev/mapper/vg-home /home ext4 defaults,noatime,nodiratime 0 2 |
10.4 Le scheduleur
Utilisez simplement le deadline. Le CFQ n'est pas optimal (bien qu'il ai été revu pour les SSD), nous n'allons pas faire travailler inutilement. Ajoutez l'option elevator :
10.4.1 Détection de SSD
Il est possible grâce à UDEV de définir automatiquement le scheduleur à utiliser en fonction du type de disque (a plateaux ou SSD) :
10.5 Limiter l'écriture
Nous allons également limiter l'utilisation des écritures disque en mettant du tmpfs là ou les fichiers temporaires sont souvent écrit. Insérez donc ceci dans votre fstab :
/etc/fstab |
[...] tmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0 tmpfs /var/lock tmpfs defaults,noatime,mode=1777 0 0 tmpfs /var/run tmpfs defaults,noatime,mode=1777 0 0 |
11 References
http://www.mjmwired.net/kernel/Documentation/block/queue-sysfs.txt
http://www.ocztechnologyforum.com/forum/showthread.php?85495-Tuning-under-linux-read_ahead_kb-wont-hold-its-value
http://static.usenix.org/event/usenix07/tech/full_papers/riska/riska_html/main.html