Moin,
ich habe ein Problem: Das Aktualisieren der Anzeigen dauert ewig. In einer von mir betreuten Community dauert es beispielsweise über 8 Stunden, die Reaktionen (ca. 4.5 Millionen) zu aktualisieren, wogegen das eigentlich innerhalb von zwei Stunden erledigt sein sollte. Von den über 8 Millionen Beiträgen im Forum will ich gar nicht erst anfangen. Im Einsatz war bisher übrigens MariaDB 10.3, jedoch habe ich letzte Nacht auf MySQL 8 migriert. Das hat zwar nichts geändert, aber wer weiß, wofür das irgendwann noch einmal gut sein wird.
Nun habe ich mir sagen lassen, dass das Problem ein Bottleneck sein könnte, allerdings konnte ich auf die Schnelle nichts finden. Ich habe zur Ermittlung iostat verwendet:
$ iostat -x -c -d -t 1 360 > iostat.txt
$ grep avg-cpu -A1 ~/iostat.txt | grep -v "avg-cpu" | grep -v "-" | awk '($6+$4)<50.0{printf("%5.1f\n", $6+$4)}' | wc -l # 50 weil 2 Threads pro Core
0
Auch habe ich mir sagen lassen, dass sich das Problem vermutlich nicht durch Anpassen der MySQL-Konfiguration (isv. Caching, etc.) beheben ließe, weshalb ich mich dazu entschlossen habe, es mal mit ZFS zu probieren.
Der Plan: Einen Hetzner Cloud-Server mitsamt entsprechend großem Volume aufsetzen und das Volume dann exklusiv für MySQL mit ZFS konfigurieren.
Das Problem: Bisher kannte ich ZFS nur vom Namen und ein bisschen lesen. Daher sind mir zwar die Vorteile von ZFS weitesgehend geläufig, aber dann hört es auch schon fast wieder auf.
Also habe ich einen Server (Ubuntu 20.04) aufgesetzt und ZFS (zfsutils-linux) installiert. Dann schon die erste Hürde: Das Volume konnte nicht für ZFS verwendet werden, weil es mit ext4 formatiert und eingehangen war. Also habe ich das mittels einem simplen umount /dev/sdb ausgehangen, woraufhin ich dann auch den Pool erstellen konnte:
So weit, so gut. Oder anders: Ob das so richtig war, weiß ich nicht mit Sicherheit. Zumindest gab es keine Fehlermeldung bei der Pool-Erstellung und der Status scheint auch in Ordnung:
$ zpool status
pool: mysql-server
state: ONLINE
scan: none requested
config:
NAME STATE READ WRITE CKSUM
mysql-server ONLINE 0 0 0
sdb ONLINE 0 0 0
errors: No known data errors
Display More
Anschließend habe ich noch zwei Container hinzugefügt und das Ganze dann entsprechend konfiguriert:
$ zfs create mysql-server/log
$ zfs create mysql-server/data
$ zfs set atime=off mysql-server
$ zfs set compression=lz4 mysql-server
$ zfs set logbias=throughput mysql-server
$ zfs set primarycache=metadata mysql-server
$ zfs set recordsize=16k mysql-server
$ zfs set xattr=sa mysql-server
Da InnoDB selbst Prefetching kann, habe ich das ZFS Prefetching deaktiviert:
Gerade in Cloud-Umgebungen scheint das empfohlen zu sein, wenn die Festplatten-E/A stark eingeschränkt- und die Bereitstellung weiterer IOPS teuer ist.
---
Nun habe ich MySQL installiert (hätte ich auch vorher tun können, ist ja gehpost, wie gesprungen) und danach die Dateien in ZFS verschoben und die Mountpoints gesetzt:
$ mv /var/lib/mysql/ib_logfile* /mysql-server/log/
$ mv /var/lib/mysql/* /mysql-server/data/
$ zfs set mountpoint=/var/lib/mysql mysql-server/data
$ zfs set mountpoint=/var/lib/mysql-log mysql-server/log
$ chown mysql.mysql /var/lib/mysql /var/lib/mysql-log
Anschließend musste ich die MySQL-Konfiguration entsprechend anpassen:
[client]
# ZFS FILE/DIRECTORY SETTINGS #
socket = /var/lib/mysql/mysql.sock
[mysqld]
# ZFS FILE/DIRECTORY SETTINGS #
datadir = /var/lib/mysql
innodb_log_group_home_dir = /var/lib/mysql-log
socket = /var/lib/mysql/mysql.sock
log_error = /var/lib/mysql-log/error.log
log_bin = /var/lib/mysql-log/binlog
relay_log = /var/lib/mysql-log/relay-bin
slow_query_log_file = /var/lib/mysql-log/slow.log
# ZFS OPTIMIZATIONS #
# In order to prevent torn pages and avoid read-on-write overhead,
# we should set this to the underlying file system block size.
# If we are keeping the InnoDB logs in the data directory (as is the default),
# we should set this to what we set the ZFS recordsize to.
innodb_log_write_ahead_size = 16384
# InnoDB double-write has one purpose – to prevent torn pages from corrupting the data in case of application crash.
# Because commits on ZFS are atomic, and we have aligned the InnoDB page size and innodb_log_write_ahead_size with ZFS recordsize,
# a torn page cannot occur – either the entire block is written, or the entire block is lost.
# This eliminates the risk of a partial page being written, so we can disable innodb_doublewrite.
#
# Note: Tim Düsterhus said, it might be a good idea not to disable it, so let's comment it out for now.
#innodb_doublewrite = 0
# InnoDB records a checksum in each page. This is important for detecting data corruption on traditional storage stacks,
# but ZFS already does the same thing – it computes and stores the checksum of eack block.
# Doing it at InnoDB level as well is therefore rendered redundant and can be disabled for a small CPU saving.
innodb_checksum_algorithm = none
# When it is time to flush out a page to disk, InnoDB also flushes all of the nearby pages as well.
# This is beneficial on spinning rust and with traditional file systems,
# but with a copy-on-write file system like ZFS where logically nearby blocks are not necessarily physically nearby blocks,
# this typically just wastes disk I/O. It is better to disable it on ZFS, even when using mechanical disks.
innodb_flush_neighbors = 0
# On Linux, the ZFS driver’s AIO implementation is a compatibility shim.
# InnoDB performance takes a hit when using the default code path.
# To fully disable the use of ZFS native AIO, innodb_use_atomic_writes=0 should also be set.
innodb_use_native_aio = 0
Display More
All set. Dachte ich. Denn scheinbar fehlten dem ganzen nun irgendwelche Rechte, denn der MySQL-Server ließ sich aufgrund von Permission-Fehlern nicht starten. Daher habe ich noch den Teil
# Allow log file access
/var/log/mysql.err rw,
/var/log/mysql.log rw,
/var/log/mysql/ r,
/var/log/mysql/** rw,
in der Datei /etc/apparmor.d/usr.sbin.mysqld ausgetauscht gegen:
# Allow log file access
/var/log/mysql/ r,
/var/log/mysql/** rw,
/var/lib/mysql-log/ r,
/var/lib/mysql-log/** rwk,
Und siehe da: Der MySQL-Server startet ohne Fehler.
----
Nun war alles bereit und ich habe die rund 10 GB große Datenbank auf dem "alten" Server exportiert und dann auf dem ZFS-Test-Server eingespielt. Hat etwas gedauert, habe ich aber auch nicht anders erwartet. Andererseits habe ich zumindest für den Import noch spezielle Einstellungen in MySQL festgelegt:
Ob ich hierfür auch sync=disabled in ZFS hätte setzen sollen, weiß ich nicht mit Sicherheit, ich habe es nicht getestet.
Am Ende waren die Daten aber eingespielt und soweit schien auch alles zu funktionieren:
$ zfs list
NAME USED AVAIL REFER MOUNTPOINT
mysql-server 12.3G 35.6G 24K /mysql-server
mysql-server/data 6.56G 35.6G 6.56G /var/lib/mysql
mysql-server/log 5.77G 35.6G 5.77G /var/lib/mysql-log
$ zfs get compressratio,used,logicalused mysql-server/data
NAME PROPERTY VALUE SOURCE
mysql-server/data compressratio 1.86x -
mysql-server/data used 6.56G -
mysql-server/data logicalused 12.2G -
$ zfs get compressratio,used,logicalused mysql-server/log
NAME PROPERTY VALUE SOURCE
mysql-server/log compressratio 2.06x -
mysql-server/log used 5.77G -
mysql-server/log logicalused 11.9G -
Display More
Ich wollte das Ganze nun endlich testen und hatte die große Hoffnung, dass ich mit diesem Setup eine signifikante Verbesserung (10-20%) des Prozesses zum Aktualisieren der Anzeigen feststellen würde. Aber Pustekuchen. Gefühlt ist genau das Gegenteil eingetreten und das Aktualisieren der Anzeigen hat noch viel länger gedauert, als im "normalen" Setup. Es ist also nicht ein bisschen besser, sondern wesentlich schlechter geworden, weshalb ich nun diesen Thread hier verfasse, weil es ja doch einige gibt, die sich im Gegensatz zu mir schon ausgiebig mit ZFS befasst haben.
Habe ich in der Umsetzung irgendeinen Fehler gemacht? Irgendwas vergessen? Oder liegt das Problem vielleicht darin, dass der Server, auf dem ich das aufgesetzt habe, ein externer ist (der Server mit der Live-Community steht in Falkenstein, der ZFS-Test-Server in Nürnberg) und dadurch die Latenz zu hoch ist?
Das war's vorerst. Danke für's Lesen