The Revenge of Mariadb IV

Es ist mal wieder Zeit für eine MariaDB Geschichte. Keine Panik, die wird nicht wieder biblisch werden 🙂

Es war mal wieder Zeit für ein OS-Upgrade

Mittlerweile waren drei OS-Releases ins Land gegangen und eine sich anbahnende Sicherheitslücke im Exim .. PSSST! Ich habe sie Euch nicht verraten. Tut so, also wenn Ihr nächste Woche das erste mal davon gelesen habt, ja? 😉 .. zwang zu einem schnellen Update auf eine nicht betroffene Version. Fedora 29 sollte es sein 😉

Nun, glücklicherweise war auch ein anderer Testserver noch auf der gleichen alten OS Release, so daß er als Testlauf herhalten durfte. Dieser Test lief ausgesprochen positiv ab, um genau zu sein, makellos. Daher wurde kürzestfristig entschieden, auch den eigentlichen Kandidaten auf die gleiche Art zu updaten. Ein Snapshot der VM und 30 Minuten später  war das Update komplett und der Server startete wieder.

Mailserver, Webserver, IMAP-Server, diverse Dienste liefen. Was natürlich nicht lief war die MariaDB .. Ätzzz. N I C H T  S C  H O N  W I E D E R!   Auch gutes Zureden, energisches Nachtreten mit systemctl änderte nichts an diesem Status, der Datenbankserver startete nicht.

Same procedure als last year, Miss Sophie?

Leider nein. Letztes mal hatte sich bekanntlich ein Konfigfile des Systemd geändert, so daß die zum Start nötigen Limits nicht gesetzt wurden. Natürlich wurde der DIFF Test vom letzten mal auch wieder angewendet, zeigte aber keinen Unterschied an. Also mußte die gute alte Methode „Start den Dienst von Hand, dann bekommst Du Infos“ herhalten.

Wie macht man das?

Wir schauen uns mal an, was der Systemd so starten wollen würde:

cat /usr/lib/systemd/system/mariadb.service

Da findet dann das hier:

ExecStartPre=/usr/libexec/mysql-prepare-db-dir %n
# MYSQLD_OPTS here is for users to set in /etc/systemd/system/mariadb@.service.d/MY_SPECIAL.conf
# Note: we set –basedir to prevent probes that might trigger SELinux alarms,
# per bug #547485
ExecStart=/usr/libexec/mysqld –basedir=/usr $MYSQLD_OPTS $_WSREP_NEW_CLUSTER
ExecStartPost=/usr/libexec/mysql-check-upgrade

Das POST können wir ignorieren, soweit kommen wir nicht, bleiben nur Pre und der eigentliche Dienststart.

Das in den einschlägigen Envfiles  unter /etc/sysconfig/ nichts steht, reicht also ein :

/usr/libexec/mysqld –basedir=/usr

für den Test aus. Ja super.. eine Fehlermeldung:   Can’t set requested open_files (1024) to 2000 …

Das lag an dem ulimit hier:

# ulimit -a
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 32088
max locked memory (kbytes, -l) 16384
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) unlimited
cpu time (seconds, -t) unlimited
max user processes (-u) 32088
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

Damit kann nicht mal die minimale Version der Datenbank starten 😉 Ergo geben wir ein: „ulimit -n 99999“ und starten es nochmal und siehe da, es hat sich doch wieder was geändert:

2019-06-04 23:13:41 0 [ERROR] /usr/libexec/mysqld: unknown variable ‚innodb_additional_mem_pool_size=50M‘
2019-06-04 23:13:41 0 [ERROR] Aborting

Wenn man denn endlich mal in Logfile schaut, sieht man dort auch die Warnung, daß sich das ja mal ändern könnte. Wer rechnet schon mit damit, daß da jemand mal Ernst macht 😉

2019-05-26 09:18:16 b753fd80 InnoDB: Warning: Using innodb_additional_mem_pool_size is DEPRECATED. This option may be removed in future releases, together with the option innodb_use_sys_malloc and with the InnoDB’s internal memory allocator.
2019-05-26 9:18:16 3075734912 [Note] InnoDB: innodb_empty_free_list_algorithm has been changed to legacy because of small buffer pool size. In order to use backoff, increase buffer pool at least up to 20MB.

Merke, erst ins Datenbank Log schauen, dann Server updaten!

Drei OS-Versionen sind dann vielleicht doch etwas happig bei einem Update. Diese Option findet man, so man sie gesetzt hat, in der Datei /etc/my.cnf . Anweisung auskommentieren, diesmal Datenbank über systemctl starten und läuft wieder 🙂

Und hier dachte ich eigentlich, ist die Story zu ende… war sie aber nicht!

Der an diesem Master angeschlossene Replications Elf ( Sklave sagt man im PCSG nicht mehr 😉 ) mochte die Replikationsblöcke vom Master nicht mehr lesen, weil Checksumme unbekannt! Das lag daran, daß der Replikations Elfe eine ältere Datenbankversion laufen hatte, da LTS. Einer langen Suche kurzes Ende:

Elfen anhalten: mysql -p -e „stop slave
auf den Master connecten: mysql -p -e „set global binlog_checksum=’NONE‘; SHOW MASTER STATUS;“ ausführen.

+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000138 | 521505   |              |                  |
+------------------+----------+--------------+------------------+

Datenbank auf dem Master dumpen, auf den Elfen kopieren. Auf dem Elfen die Datenbank wipen, den SQL Dump einspielen.

Auf dem Elfen : mysql -p -e „change master to MASTER_LOG_FILE=’mysql-bin.000138′, MASTER_LOG_POS=521505;start slave

Sollte wieder gehen. Ging nicht.. Neuer Fehler! Statt:

Last_IO_Errno: 1236
Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Slave can not handle replication events with the checksum that master is configured to log; the first event 'mysql-bin.000136' a'

gabs jetzt :

Last_IO_Errno: 1045
Last_IO_Error: error connecting to master 'test@masterserver:3306' - retry-time: 60  retries: 86400

Er zum Geier ist „TEST“ !?

Tja, keine Ahnung wie das passieren konnte, aber der „change master to …“ Befehl hatte den Replikationsusernamen durch „test“ ersetzt! Einfach so! Passwort war ok, Servername war ok, Username weg! Wie geil ist das denn ?!?!?

Also nochmal mit dem change master an den Elfen ran:

/usr/bin/mysql -p -e „stop slave;;CHANGE MASTER TO MASTER_USER=’myreplicationusername‘,MASTER_PASSWORD=’MeinPasswort‘;start slave

Da machste was mit als Admin … Ich wollte doch nur den Exim abdichten, nicht das Rad neu erfinden! Naja, jetzt gehts ja erstmal wieder 🙂 Und bevor noch einer fragt, nein, der Server hatte extern nur Port 25 zu bieten, deswegen ja auch das Update 😉

Linux – mysqldump sieht Leerzeichen als leeren Datenbanknamen an

Erinnert Ihr Euch noch an diesen Beitrag : Solution – mysqldump – No database selected when selecting the database aus dem Jahr 2016 ?

MariaDB & mysqldump

Damals stolperte ich über einen Fehler im Kommandozeilenparsing von Mysqldump.

Wie sich herausgestellt hat, konnte der Betreuer Michael Scholm bei Redhat die Ursache für das Problem entdecken. Kommt Ihr nie drauf: Das  war Schuld ! Oh ?! Ihr habt es nicht gesehen, hier nochmal  . Vielleicht mit der Lupe, da: > <. Ein Space.  Hier … zwischen dem „usr/bin/mysqldump“ und dem „–addlocks“ ist ein “ “ zuviel drin:

/usr/bin/mysqldump  --add-locks -e --force -R ....

Da es keine Option ist, wird es tatsächlich als valider Versuch, einen Datenbanknamen anzugeben gewertet.IMHO, ein Parser-Logik-Bug. Redhat sieht das anders: „not a bug“. Da jetzt raus ist, was es verursacht, kann man es ja leicht beheben.

etwas Kontext

Die Parser für Linux sind, sagen wir mal, ein bisschen eigen und deren Interpretation auch. Als Trenner zweier Argumente einer Kommandozeile gilt ein freistehendes Leerzeichen. Die Bash-Shell entfernt doppelte Leerzeichen und setzte ein Leerzeichen an die Stelle. So ein Leerzeichen zu viel ist schnell getippt und würde zu vielen „Dicke Finger-Syndrom“-Problemen führen, weil ..

… Ja, weil der Parser ohne diesen Trick, zwei Leerzeichen in Folge so übersetzen :

ls -la  foo => ls -la "" foo

Weil „Leerzeichen“ sind Trenner, also steht da oben eigentlich: „ls{Trenner}-la{Trenner}{Trenner}foo .

Wenn man aber zwischen den Trennern nichts hat, dann kommt ein leeres Argument raus. Was ein leeres Argument bedeutet, zeigt sich hier im Beispiel:

[marius ~]$ ls -la "" foo
ls: Zugriff auf '' nicht möglich: No such file or directory
ls: Zugriff auf 'foo' nicht möglich: No such file or directory

Wie man sehen kann versucht ls tatsächlich auf aka. „“ zuzugreifen. Wie das gehen soll ? Gar nicht. Ist ein Logikbug im Kontext von ls . Es wiederspricht der Natur eines ls , etwas aufzulisten, das es nicht gibt. Ergo müßte das Argument ignoriert werden. Jeder Mensch würde das so machen. Computerprogramme sind in der Beziehung blöde. Deswegen gibt es die Bash, die das Doppel-Leerzeichen wegnormaliziert und so vorhersehbare Fehler vermeidet, weil ..

Fehler vermeiden

wie man im obigen ls Beispiel gesehen hat, kann man es ja explizit angeben, wenn man muß => „“ oder “

MariaDB zieht sich jetzt auf den Standpunkt zurück, daß mysqldump sich ja nur wie ls verhalten würde. Tut es natürlich nicht, weil mysqldump komplett failed, wo ls einfach weitermacht und einen Fehler ausgibt.  Naja, Nobodys perfect.

Update

… oh, Redhat hat die Meinung geändert und einen Featurerequest bei MariaDB für einen besseren Parser erstellt. \o/ Das gibt einen dicken Daumen rauf! Mal sehen wies bei MariaDB ausgeht.

Wie man die TMP Ramdisk entfernt

Auf normalen Desktopsystemen ist es eine gute Sache, wenn der /tmp/ Ordner im RAM liegt. Auf /tmp/ wird sehr häufig und meistens eher kleinteilig zugegriffen, so das man diese Zugriffe  am besten von der Festplatte oder der SSD fern hält. Auf einem Server kann das aber auch von Nachteil sein.

[root@server ~]# df -h
Dateisystem    Größe Benutzt Verf. Verw% Eingehängt auf
devtmpfs        5,0G       0  5,0G    0% /dev
tmpfs           5,0G       0  5,0G    0% /dev/shm
tmpfs           5,0G    532K  5,0G    1% /run
tmpfs           5,0G       0  5,0G    0% /sys/fs/cgroup
/dev/xvda1      245G    178G   56G   77% /
tmpfs           5,0G     38M  5,0G    1% /tmp
tmpfs          1012M       0 1012M    0% /run/user/0

Im obigen Beispiel von einem unserer Server, kann man sehen, daß für die „tmpfs“ Laufwerke 5 GB maximale Größe angegeben ist. DEV, RUN, SYS werden das niemals erreichen, die sind eher im KB Bereich angesiedelt. Über die Sinnhaftigkeit, dann 5 GB als MAX Größe zu nehmen, kann man sicher streiten. Ist aber für die Betrachtung egal, denn es handelt sich um eine dynamische Speicherbelegung, deswegen auch „maximale Größe“. In Real sind die genau so groß, wie die Daten darin das brauchen. Lege ich dort 1 MB ab, ist es 1 MB und ein bisschen was für die Verwaltung groß.

An der „Verwendung“ in Prozent bzw. „Benutzt“ kann man auch sehen, das oben keins der TmpFS Ramdrives übermäßig belegt war. Die Ramdrives haben also bei dem Stand zusammen grade mal 39 MB echten Speicher belegt.

So weit, so gut.

Das obige Serversystem hat 10 GB Speicher zur Verfügung, was es üblicherweise auch braucht. d.h. es sind permanent mehrere GB an RAM in realer Benutzung.

Datenbankserver wie MariaDB erlauben es den Benutzern bei Abfragen sogenannte TEMP-Tables zu erstellen. Das wird vorzugsweise im RAM gemacht. Wenn aber das RAM nicht reicht, weil jemand einen TEMP-Table zusammen baut, der mehrere GB groß ist, dann wird das in den /tmp/ Ordner ausgelagert. Und man glaubt gar nicht wie unsensible mache Anwendungsentwickler im Umgang mit solchen Temp-Tables sind. „Killer SQL-Anweisungen“ in Shops, die „ein bisschen mehr und schneller“  gewachsen sind, als die Hersteller das erwartet haben, sind keine Seltenheit. Schlechtes Datenbankdesign sowieso nicht 😉  Und damit fängt der Ärger dann üblicherweise auch an.

Was bei einem Killer-SQL passieren kann …

Der Hauptspeicher des Datenbankserver hatte schon nicht ausgereicht um den Temp-Table anzulegen, und über die Ramdisk wird jetzt versucht den Speicher zusätzlich nochmal zu belegen, der vorher schon nicht ausreichend da war. Der Kernel wird jetzt versuchen diese Datenmengen zu swappen und kann das vielleicht nicht, weil die SWAP Partition zu klein ist. Nun kommt es zum „OOM“ dem Out-of-Memory-Error. d.h. der Kernel fängt an, scheinbar wahllos Prozesse zu killen, die viel Speicher belegen, aber noch nicht lange laufen. Eine genauere Analyse nimmt der Kernel leider nicht vor.

Wie kommt man jetzt aus der Falle wieder raus ?

Verantwortlich für das Erzeugen der Ramdisk ist diese Systemd Unit : /usr/lib/systemd/system/tmp.mount

#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Temporary Directory
Documentation=man:hier(7)
Documentation=http://www.freedesktop.org/wiki/Software/systemd/APIFileSystems
ConditionPathIsSymbolicLink=!/tmp
DefaultDependencies=no
Conflicts=umount.target
Before=local-fs.target umount.target

[Mount]
What=tmpfs
Where=/tmp
Type=tmpfs
Options=mode=1777,strictatime

Die kann man mit einem kurzen Befehl an den Systemd abschalten, allerdings erst ab dem nächsten Bootvorgang:

# systemctl mask tmp.mount
Created symlink from /etc/systemd/system/tmp.mount to /dev/null.
# ls -la /etc/systemd/system/tmp.mount
lrwxrwxrwx 1 root root 9 14. Nov 11:45 /etc/systemd/system/tmp.mount -> /dev/null

Danach muß man das also Rebooten. Am Ende ist /tmp/ dann wieder ein normaler Ordner auf der Festplatte, der keiner Größenbeschränkung unterliegt und in dem der Datenbankserver dann auch wieder fast beliebig große Temp-Tables erzeugen kann, ohne das gleich ein unschuldiger Prozess dran glauben muß.

 

Wie man PHPMyAdmin beschleunigen kann

Habt Ihr auch einen „modernen“ PHPMyAdmin installiert, der bei einigen Anzeigen gefühlte Äonen braucht, um den banalen Inhalt der Datenbank anzuzeigen ?

Ja ? Habt Ihr auch schon gedacht, daß es an der DNS Auflösung liegen könnte ?
Ja ? und es hat so gar nichts gebracht ein schnelleres Cache  einzustellen ?
Ja ? Na dann schaltet doch mal auf ENGLISCH als PMA Sprache um, Ihr werdet Euch wundern !

Es ist einen :facepalm: wert, denn für jede popplige Seite, sei es per AJAX oder per URL Link, lädt PHP die komplette, wirklich MB große Übersetzung, wenn man Deutsch eingestellt hat. Da hilft auch kein Filecache mehr um das zu beschleunigen!

Wie man sowas findet

Sehen tut man das erst, wenn man mit STRACE den Apachen und die ausgeführten PHP Programme traced.

strace -e open,read -f -p `pidof httpd | sed -e "s/ /,/g"`

Es folgt so etwas, in einer schieren Unzahl von Zugriffen, weil PHP echt groß ist und ne Menge einlesen muß.

[pid 10052] open(„/usr/lib/php/modules/intl.so“, O_RDONLY|O_CLOEXEC) = 3
[pid 10052] read(3, „…………………………….jede Menge Text ………………“, 512) = 512

Das oben ist keiner der verdächtigen Zugriffe, zeigt aber, wie man die READs den Dateien zuordnet. Das Orangerote ist der FILEDESCRIPTOR, kurz FD.  Jede Datei die man öffnet als Programm, bekommt einen eindeutigen FD, damit man das als Programm zuordnen kann.

Typisch für PHP und die Übersetzungen sind diese Zeilen :

[pid  9436] lseek(4, 93205, SEEK_SET)   = 93205
[pid  9436] lseek(4, 93205, SEEK_SET)   = 93205
[pid  9436] read(4, „Database %s has been dropped.\0Database Log\0Database client version:\0Database comment\0Database for user account\0Database level tabs\0Database name\0Database name template\0Database operations\0Database seems to be empty!\0Database server\0Database structure\0Database system or older MySQL server to maximize output compatibility with:\0Database tree separator\0Database used for relations, bookmarks, and PDF features. See [a@http://wiki.phpmyadmin.net/pma/pmadb]pmadb[/a] for complete information. Leave blank for no support. Suggested: [kbd]phpmyadmin[/kbd].\0Database-level tabs\0Database-specific privileges\0Database:\0Databases\0Databases display options.\0Databases statistics\0Databases:\0Date\0Deactivate now\0Deactivate tracking for %s\0Debug SQL\0Dec\0December\0Default\0Default database tab\0Default format; be aware that this list depends on location (database, table) and only SQL is always available.\0Default language\0Default server\0Default server tab\0Default sort order for tables with a primary key.\0Default sorting order\0Default ta“…, 8192) = 8192

Warum PHP 8KB Blöcke einliest, statt min. 64KB wird ewig ein Geheimnis bleiben, scheint aber ein Relikt aus den Anfängen zu sein 🙂

Es gilt, wenn der einzulesende Datenblock, der Blockgröße auf im Filesystem entspricht, dann kann das OS den Datenstrom am effizientesten einlesen. Der Datenzugriff sollte nicht größer als die Blockgröße sein, wobei ganze Mehrfache der Blockgröße ok wären. Wenn jetzt wie bei PHP 8KB eingelesen werden, die Datenblockgröße aber 64 KB ist, braucht das OS schon 8x soviele Filezugriffe nebst Performanceoverhead durch die Lesefunktionen, um den gleichen Inhalt eines Blockes einzulesen. Dazu muß die Platte den Kopf 8x zu dem Datenblock fahren, falls auf einem System noch mehr los ist, als nur dieses einzige Programm. Was man getrost annehmen kann.

Sowas ist einfach ineffizient. Ganz besonders ineffizient, wenn die Datei, die man da einlesen will, mehrere MB groß ist.

Ich hoffe, es hilft Euch ein bisschen bei Euren eigenen Problemen.

 

 

SQLITE Disk IO Error

Manchmal ist es zum Mäuse melken und der Fehler „General error: 10 disk I/O error“ vom SQLLite PDO Teiber von PHP gehört dazu:

Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY000]: General error: 10 disk I/O error'

Wenn Sie das bekommen, schauen Sie doch mal nach, ob Ihr User auch genug Speicherplatz zur Verfügung hat und die Daten zu schreiben. Lassen Sie sich nicht davon täuschen, daß Sie als Root User in der Shell direkt mit dem SQLLite Befehl in der Datenbank sauber schalten und walten können 😉