Ein Blog über Code, Hardware und Co

Wordpress und Webentwicklung, Programmieren und Entwickeln, Technik und Hardware

Hohe I/O-Last bei MySQL/MariaDB in den Griff bekommen

Oder: Über tmp_disk_tables und eine Ramdisk

Ich habe auf meinem Server eine ganze Reihe an Webseiten laufen. Da ist es das A und O, dass alle Webseiten schnell laden und trotzdem der Server (auf dem noch weitere Dienste laufen) stabil und reaktionsschnell läuft.

Das Problem: Die Festplatten am Anschlag

Vor kurzem ist mir aufgefallen, dass das nur noch eingeschränkt der Fall ist. Sobald der Webserver und die Webseiten (und damit der MariaDB-Server) laufen, geht die IO-Last massiv nach oben.
Teilweise so extrem, dass selbst der eigentlich verwendete SSD-Cache nicht richtig greift und andere Dienste auf dem Server leiden.

Bild 1: Hohe I/O-Last auf den Festplatten (Hier exemplarisch auf einer Platte des laufenden Raids).

Wieso ist die IO-Last so hoch?

Nun müssen wi der Frage auf den Grund gehen, wieso die Last so hoch ist.
Der Übeltäter ist mittels iotop schnell gefunden: MariaDB.

Bild 2: MariaDB scheint Spaß am Schreiben zu haben und erzeugt dadurch hohen IO-Load.

Insbesondere scheint MariaDB besonders viel zu schreiben. Und das trotz ausreichend RAM und eigentlich durchaus gutgemeinten Cache-Größen 😉

OK, also schauen wir und mal an, was MySQL bzw. MariaDB da so treibt. Ein hoher I/O-Load auf den Festplatten deutet in der Regel darauf hin, dass der schnelle RAM-Cache einfach zu gering dimensioniert ist und daher temporäre Tabellen auf die Festplatte geschrieben werden müssen (Autsch :/).

Mittels PHPMyAdmin können wir uns nun schnell einen Überblick über den aktuellen Zustand des Servers machen. Wir navigieren als über PHPMyAdmin zu “Server” -> “Status” und klicken auf “Alle Status Variablen”. Uns interessiert besonders der Wert zu “Created tmp disk tables” bzw. dessen Verhältnis zu “Created tmp tables”.

Created tmp tables = Anzahl der temporären Tabellen, die direkt im Arbeitsspeicher/RAM erzeugt worden sind.

Created tmp disk tables = Anzahl der temporären Tabellen, die auf der Disk, also den Festplatten erzeugt werden.

Bild 3: Anzahl der im RAM und auf der Festplatte erstellten temporären Tabellen auf meinem MySQL-Server.

OK, das ist eindeutig. Es werden viel zu viele temporäre Tabellen auf den Festplatten, statt im schnellen RAM erzeugt. Da muss man doch etwas machen können.

Der erste Anlaufpunkt: tmp_table_size und max_heap_table size

Der erste Gedanke ist nun naheliegend: Der Cache ist zu klein. Die hier relevanten Statusvariablen sind:

  • tmp_table_size
  • max_heap_table_size

Tipp: Diese Werte sollten in der Regel identisch sein, für unser Problem ist immer der niedrigste Wert der relevante Grenzwert.

Ok, also Testweise die Werte in astronomische Höhen getrieben um zu schauen, was die Auswirkungen sind.
Und siehe da: Nichts.

Die Caches sind in diesem Fall also groß genug. Wo kann das problem liegen.

Was sind das eigentlich für Datenbanken?

Also schaue ich mir die entsprechenden Datenbanken und Tabellen mal etwas genauer an. Es handelt sich dabei z.B. um ein Diskussionsforum.
Beim anschauen der Tabellen fällt auf, dass die ganzen dort geposteten Beiträge und Nachrichten allesamt in einem Feld vom Typ “Text” gespeichert werden.

Und hier liegt das Problem: Der schnelle Memory von MySQL, also die Caches, können mit “Text” und Blob-Feldern nichts anfangen.
Diese werden – selbst wenn eigentlich die InnoDB-Engine verwendet wird – als temporäre MyISAM-Tabellen auf der Disk gespeichert.

OK, was nun? Kein Wunder, dass der Cache nicht richtig greifen will.

Die Lösung: Das tmp-Verzeichnis in eine RAMdisk

OK, wie können wir die temporären Dateien in den schnellen RAM bekommen, wenn es über den Cache nicht geht?

Über eine RAM-Disk!

Was ist eine RAMdisk?
Eine Ramdisk ist ein virtuelles Laufwerk/Filesystem, das im RAM einer Maschine angelegt wird. Es kann danach wie ein normales Laufwerk/Verzeichnis genutzt werden, die Daten werden aber im schnellen RAM gespeichert - mit den entsprechend guten Zugriffszeiten und den astronomischen IO-Reserven ;).

Es gibt zwei Typen von RAMdisks, tmpfs und ramfs. Ich nutze und empfehle immer tmpfs.

Aber Achtung: RAM ist flüchtig. Das bedeutet, nach jedem Neustart ist die Ramdisk wieder leer!

MySQL macht es uns zum Glück relativ einfach, indem alle temporären Tabellen in einem tmp-Verzeichnis gespeichert werden.

Wir können also eine RAMdisk erstellen und dann das temporäre Verzeichnis dort ablegen.

1. Mountpoint erstellen

Wie bei Linux üblich, müssen wir zunächst einen Mountpoint für unsere Ramdisk erstellen. Das ist im Prinzip nichts anderes als ein zu erstellendes Verzeichnis:

mkdir -p /mnt/mysqlramdisk

2. Ramdisk erstellen

Jetzt erstellen wir mit folgendem Befehl eine Ramdisk, die wir am eben erstellten Mountpoint einhängen:

mount -t tmpfs -o size=512M tmpfs /mnt/mysqlramdisk

Der Ramdisk geben wir nun noch den passenden Owner (Besitzer):
chown mysql:mysql /mnt/mysqlramdisk

3. Ramdisk persistent machen

Manuell erstellte und gemountete Laufwerke bzw. Ramdisks werden nach jedem Neustart verschwinden. Daher müssen wir folgenden Eintrag in der /etc/fstab vornehmen, damit die Ramdisk nach jedem neustart wieder neu eingebunden wird.

tmpfs /mnt/mysqlramdisk tmpfs rw,mode=1777,size=512M 0 0

Besonderheit bei Synology NAS!
Bei Synology NAS ist es nicht sinnvoll die fstab zu nutzen, da diese bei einem Neustart oft neu erstellet wird.

Wir können uns aber mit dem Aufgabenplaner des NAS helfen.
Zunächst erstellen wir ein Skript, das manuell die Ramdisk erstellt und Mountet. Also im Prinzip Schritt 2 als Skript:

#!/bin/bash
mount -t tmpfs -o size=512M tmpfs /mnt/mysqlramdisk
chown mysql:mysql /mnt/mysqlramdisk

Das Skript speichern wir nun z.B. in unserm Home-Verzeichnis unter “mount-ramdisk.sh”

Anschließend erstellen wir eine neue Aufgabe im Aufgabenplaner des NAS. Die Aufgabe wird beim Hochfahren ausgeführt und ist vom Typ “Ausgelöste Aufgabe” und ein “Benutzerdefiniertes Skript”.

Das Skript muss als Root ausgeführt werden und hat den folgenden Befehl als Inhalt (die Ausführung des zuvor erstellen Mountskripts):
bash /var/services/homes/username/mount-ramdisk.sh

PS: “username” im Pfad muss natürlich mit dem entsprechenden Nutzer ausgetauscht werden.

4. Verzeichnis für temporäre Dateien ändern

Zu guter Letzt müssen wir jetzt noch das temporäre Verzeichnis von MySQL anpassen. Das machen wir ganz einfach über die Config-Datei: my.cnf

tmpdir = /mnt/mysqlramdisk

Und danach noch MySQL neustarten. Fertig.

Schreibe eine Antwort