Wie man rausbekommt, wer ausgeswappt wurde

Wir kennen es alle, Zig GB Ram in Hardware verbaut und die Kiste swappt trotzdem. Da will man irgendwann wissen, wer/welcher Prozess dafür verantwortlich ist. Aber der Reihe nach:

Was ist „Swappen“ ?

Dazu müssen wir erstmal wissen, was virtuelles Ram ist. Physikalisches Ram kann sich sicher jeder vorstellen, das kann man als Baustein in die Hand nehmen 😉 Selbiges ist z.b. 10 GB groß ( siehe unten / nicht fragen ist ne VM )

top - 16:26:05 up 17:16,  1 user,  load average: 0,75, 0,56, 0,53
Tasks: 413 total,   1 running, 411 sleeping,   0 stopped,   1 zombie
%Cpu(s):  8,3 us,  4,0 sy,  0,0 ni, 86,5 id,  0,4 wa,  0,4 hi,  0,1 si,  0,2 st
KiB Mem : 10264728 total,   563348 free,  3256528 used,  6444852 buff/cache
KiB Swap:  1048572 total,   739584 free,   308988 used.  6788296 avail Mem

d.b. „physikalisch“ stehen allen Programmen zusammen maximal 10 GB Hauptspeicher zur Verfügung, mit dem sie auskommen müssen. Wenn alle RAM Speicherzellen benutzt werden und jemand will mehr Speicher haben, gibt es einen OOM Error : Out Of Memory! und die Aktion schlägt fehl, was Programme üblicherweise übel nehmen und die weitere Zusammenarbeit verweigern. Soweit, so klar.

Jetzt gibt es aber Programmierer, die mehr Speicher anfordern, als ihre Anwendung braucht. Der RAM wird „nutzlos“ belegt und steht anderen Programmen nicht zur Verfügung. Entweder müßte man GB weise Ram nachrüsten, oder man tut nur so als wenn man Speicher hätte 🙂

Virtueller Speicher

Auch VMEM genannt, ist nichts anderes als die Illusion, daß man mehr Speicher hat, als man hat. Technisch gesehen ist, das einfach: Bevor ein Programm nichts in den Speicher, den es anforderte, geschrieben hat, muß es den Speicher nicht wirklich geben. Man kann dem Programm einfach sagen, daß seine Anforderung nach 10 TB Ram erfolgreich war. Solange es die 10 TB nie benutzt, ist alles ok.

Wird von dem Programm etwas in seinen Speicherbereich geschrieben, so merkt dies das OS und kann den Speicherblock tatsächlich physikalisch machen. Eine MMU ( Memory Management Unit ) in der CPU macht das. Die mapped nämlich den echten Physikalischen Speicherbereich in den Virtuellen Speicherbereich, so daß die Schreibzugriffe an Adressen gehen, die es nicht wirklich gibt, aber so umgebogen werden, daß sie an der richtigen Stelle landen, alles hardwaremäßig.

Das klappt solange, wie der real genutzte Speicher kleiner ist, als die Menge verbauten Hauptspeichers.

Der Notfall: Einen OOM verhindern.

Kurz vor dem Punkt, wo das System keinen Speicher mehr hätte, wird geprüft, wann einzelne Speicherbereiche das letzte mal gelesen oder geschrieben wurden. Wenig verwundernd stellt man dann fest, daß es Speicherblöcke gibt, die in letzter Zeit nicht benutzt wurden.

Idee: Die könnte ich doch irgendwo in einem langsamen Speichermedium mit viel Platz auslagern, oder ? Gedacht, getan: Das Swappen wurde aktiviert und meint, daß bei Speichermangel ungenutzte Teile auf die Festplatte geschrieben werden und andere Teile, die dort schon lagen, wieder eingelesen werden. Man tauscht(swap) also die aktiven und inaktiven Speicherbereiche mit Hilfe der Platte aus.

Swappen

Dieser Vorgang heißt „swappen“. Es wird also ständig auf die Platte geschrieben und davon gelesen. Das hört sich erstmal nicht nach einem Problem an. Ist es aber, weil wenn soviel verschiedenes RAM ständig geswappt wird, wird das System langsam, schliesslich muß man warten bis dieser Vorgang vorbei ist. Wenn es langsam wird, stauen sich die Prozess: Die Load/Last steigt.

Die Idee beim Swapspeicher ist, daß man lange ungenutzte Speicherblöcke auslagert und die auch nicht gleich wieder benutzt werden. Auf einem Desktoprechner ist der Klassiker, daß der Speicher frei ist, bis Photoshop ein 50MP Bild lädt. Das geht vielleicht noch ins RAM, aber Browser, Mailprogramm, Kalender usw. nicht mehr. Da man aber gerade mit Photoshop arbeitet, ist das auch nicht schlimm, denn es ist nicht zu erwarten, daß ich vor Beendigung der aktiven Aufgabe von Photoshop eins der anderen Programme brauche. I.d.R. klappt das auch. Das führt zur irrigen Annahme, daß viel Swapspeicher = „total geile Sache“ ist.

Auf einem Server ist das etwas anderes. Da kann man nicht wirklich vorhersehen, wann ein Dienst gebraucht wird und da ist swappen zu vermeiden. Weil wenn viel Swapspeicher vorhanden ist, dauert es auch entsprechend lange, bis der voll beschrieben oder gelesen ist. d.h. der Rechner steht praktisch, nur weil er Speicher auf die Platte schreibt.

Auf einem Server ist es deutlich cleverer, wenig Swapspeicher zu haben, damit tatsächlich ein OOM entsteht, wenn es nötig ist. Kleinere temporäre Swaps sollten aber möglich bleiben. Deswegen ist der Swapspeicher im Beispiel oben auch nur 1 GB groß. Die Regel, daß der Swap 50% vom Hauptspeicher groß sein sollte, war im MB Bereich charmant, mit GB aber nicht mehr ganz so gut.

Wie verhindert man das Swappen ?

Mal davon abgesehen, daß man das Abschalten und auch das Verhalten des Swappens im Kernel beeinflussen kann, wäre es viel cleverer den Grund für das Swappen zu finden und das geht nur, wenn man weiß wer gerade geswappt wird.

Bevor die Kommentare losgehen: Das Programm, dessen Speicher auf dem Swapspeicher landet, muß nicht jenes sein, daß zuviel RAM wollte. Aber es ist das Programm auf das man am ehesten verzichten kann 😉 Das es überhaupt im Swap gelandet ist, beweist, daß es nicht aktiv war/ist und deswegen müßte man mal nachdenken, ob man es überhaupt braucht. Dazu muß man aber erstmal wissen, wer da gelandet ist.

SMEM – die Speicheranzeige

SMEM ist ein Tool, daß man sich per DNF nachinstallieren muß. Es produziert so eine Map :

  PID User     Command                         Swap      USS      PSS      RSS 
  557 root     /sbin/rngd -f                    156        4        7     1292 
  752 root     /sbin/agetty --noclear tty1      128        4       12     1692 
  753 root     /sbin/agetty --keep-baud 11      152        4       35     2184 
 1690 mailman  /usr/bin/python /usr/lib/ma    30000        4       51     1676 
  614 root     /usr/sbin/atd -f                 180       52       66     2104 
18675 root     sleep 60                           0       80       97     1312 
19506 root     sleep 30s                          0       84      126     1736 
 1467 samba    /usr/sbin/smbd                  2048       16      323     2868 
 9514 50779    dovecot/imap                       0      312      340     3828 
 1659 nobody   proftpd: (accepting connect     2076      144      349     2148 
12494 root     dovecot/log                        0      348      355     2544

In der Liste kann man schön sehen, daß Mailman schön hervor sticht. Mailman ist ein Mailinglistenmanager. Den braucht man nur ganz selten. Da müßte man mal über einen Umzug nachdenken. Die Liste oben ist natürlich nur ein Auszug und wenig aussagekräftig. Im weiteren Verlauf tauchte Mailman dann entsprechend oft auf.

Hinweis: DAS ein Programm mal den einen oder anderen Block ausgelagert bekommen hat, ist nicht weiter dramatisch. Das gibts öfter und ist auch kein Problem, weil diese Teile üblicherweise nur beim Start wichtig waren und später einfach nicht mehr benutzt werden.

Wenn man den Verursacher des Speicherverbrauchs sucht, geht man anders vor:

watch -n 1 „top -c -b -n >> /tmp/memory.log“  oder watch -n 1 „smem >> /tmp/memory.log“

Wobei im Einzelfall „-n 1“ vielleicht ein bisschen zu oft ist und man jede Menge ungewollte IO generiert. Da müßt Ihr etwas auf Eure Bedürfnisse achten.

Aus der Ausgabe kann man dann mit AWK den Namen des Programms, den Speicherverbrauch und den Zeitpunkt ermitteln. Ein bisschen Bashmagie und man bekommt schnell mit, wer der Verursacher ist.

Kleines Beispiel auf dem Weg zum eigenen Terminal :

# smem | grep -E " [0-9]{6,} "
3983 root     spamd chil                         0    42344    47397   104600
8016 root     spamd chil                         0    44636    49667   106744
3937 root     spamd chil                         0    44924    49907   106584
4158 root     spamd chil                         0    45352    50354   107176
2033 exim     /usr/sbin/clamd -c /etc/cla      772   497848   498045   501436
14335 mysql   /usr/libexec/mysqld --based        0   853144   853421   858820
4269 nobody   java server/Server                 0  1175172  1175819  1179580

Das gibt einem nur die Zeilen aus, in denen mehr als fünfstellige Zahlen vorkommen, weil den Kleinkram suchen wir ja nicht.

In dem Beispiel oben lohnt sich der Blick auf den Clam AntiVirusprozess clamd. Der verbraucht mittlerweile 750MB RAM nur durch den Start und der größte Block davon ist meistens unbenutzt. Clamd bietet sich als Ziel der Aktion auch deswegen an, weil er netzwerkfähig ist. Man kann also leicht einen zweiten Server aufbauen, der nur Clamd laufen hat und zu dem alle anderen Server hingehen und Ihre Mails auf Viren checken lassen.

So bräuchte man die 750 MB nicht mehr auf jedem Mailserver verbraten, sondern nur einmal. Natürlich setzt das voraus, daß der Server mit dem Clamd auch schnell etwaige Anfragen beantwortet. Aber das ist ein anderes Thema.

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ß.