Es taucht gelegentlich die Frage auf, was overcommit_memory unter Linux darstellt, welche Form verwendet wird und warum. Daher schauen wir uns das nun einmal näher an.

Overcommit beim Speicher bedeutet, dass mehr Speicher an Programme vergeben werden kann, als das System überhaupt besitzt. Da stellt sich einem die Frage, warum so etwas gut sein sollte. Die Kerneldokumentation schreibt zu diesem Punkt:

This feature can be very useful because there are a lot of programs that malloc() huge amounts of memory just-in-case and don’t use much of it.

Mit anderen Worten: Vielen Programmieren ist der wirkliche Speicherbedarf ihrer Programme unklar und sie reservieren einfach einmal reichlich davon. Wenn der Speicherplatz dann nicht vorhanden ist, starten sie nicht. Wenn es dann noch viele solcher Programme gleichzeitig auf einem System gibt, wird es richtig schwierig.

Dank overcommit können diese nun starten und laufen auch, da sie den Speicher oftmals wirklich nicht benötigen, sie greifen darauf gar nicht zu.

So gesehen, bewirkt das overcommit des Speichers eine Lösung zu einem Problem, welches unsorgfältig arbeitende Programmierer verursacht haben.

Linux bietet drei Arten von overcommit_memory an. Diese werden per sysctl oder dem proc-Dateisystem gesetzt oder ausgelesen. Die Einstellungen sind dann bei proc in der Datei /proc/sys/vm/overcommit_memory zu finden.

Die Voreinstellung ist dabei der Wert 0. Die Kerneldokumentation sagt dazu:

Heuristic overcommit handling. Obvious overcommits of address space are refused. Used for a typical system. It ensures a seriously wild allocation fails while allowing overcommit to reduce swap usage. root is allowed to allocate slightly more memory in this mode. This is the default.

Das Kritische ist hier das heuristic im Satz. Der Kernel überlegt sich hier, wieviel Speicher er sinnvoll per overcommit vergeben könnte. Das ist ein Kompromiss zwischen völligem overcommit und gar keinem.

Das völlige overcommit wird durch den Wert 1 ermöglicht:

Always overcommit. Appropriate for some scientific applications. Classic example is code using sparse arrays and just relying on the virtual memory consisting almost entirely of zero pages.

Damit bekommt ein Programm immer Speicher zugeteilt, egal wie groß dieser ist. Schwierig wird es hier, wie im obigen Fall, wenn auf den Speicher auch zugegriffen werden soll.

Die letzte Option stellt der Wert 2 dar, hier ist gar kein overcommit möglich, dann muss der Speicher auch vorhanden sein, wenn er angefordert wird. Anderenfalls liefert malloc() einen NULL-Pointer zurück und die Software kann darauf reagieren:

Don’t overcommit. The total address space commit for the system is not permitted to exceed swap + a configurable amount (default is 50%) of physical RAM. Depending on the amount you use, in most situations this means a process will not be killed while accessing pages but will receive errors on memory allocation as appropriate.

Hier gibt es gleich noch eine Einschränkung mehr: Der Speicher hat auch eine Obergrenze, ein Programm kann nicht allen Speicher allokieren. Die Kerneldokumentation erwähnt auch explizit, wann das verwendet werden sollte:

Useful for applications that want to guarantee their memory allocations will be available in the future without having to initialize every page.

Mit anderen Worten: Speicher der angefordert und vom Kernel bewilligt wurde ist auch immer vorhanden.

Praktischer Test

Mit dem Wissen, kann das nun durchgetestet werden. Dazu verwende ich einfach einmal ein kleines Programm:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define SIZE 1024*1024*256
long long count=1;

int main()
{
   char *buf;
   buf=malloc(SIZE);
   if (buf==NULL) {printf("first malloc failed\n"); exit(-1);}
   while (1) { 
     count++;
     printf("allocated: %lu MB\n",count*256);
     buf=realloc(buf,count*SIZE);
     if(buf==NULL) {
        printf("failed to allocate %lu memory\n",count*SIZE);
        exit(-1);
     }
     if (count > 400) {
         printf("100 GB of RAM allocated, stopping\n");
         exit(0);
     }
   }
}

Was macht dieses kleine Programm?

Es wird ein Speicherbereich von 256 MB angefordert:

buf=malloc(SIZE);

Danach wird geprüft, ob dieser Speicher bewilligt wurde. Ist das der Fall, wird nach und nach durch

buf=realloc(buf,count*SIZE);

jeweils der angeforderte Speicher um 256 MB vergrößert. In den erlaubten overcommit-Fällen geht das bis ans Ende des virtuellen Speicherbereiches, das kann dann auch ewig dauern. Daher habe ich noch eine Abbruchbedingung hinzugefügt:

 if (count > 400) {
     printf("100 GB of RAM allocated, stopping\n");
     exit(0);
 }

Das sind 100 GB, soviel RAM hat mein Testsystem garantiert nicht, noch nicht einmal annähernd. Daher ist diese Grenze durchaus sinnvoll.

Das ganze kann nun kompiliert werden:

$ gcc -o realloc realloc.c

Schon haben wir unser Testprogramm: realloc

Da swap zum Speicher zählt, ich aber vermeiden möchte, dass mein System unnötig auf der Festplatte herum schreibt, schalte ich diesen erst einmal ab:

# swapoff -a

Damit ist nun zu Beginn mein Speicher so ausgelastet:

# free -h
          total        used        free      shared  buff/cache available
Mem:       15Gi       2,0Gi        11Gi       262Mi       1,9Gi      13Gi
Swap:        0B          0B          0B

Es ist also duchraus schon Speicherplazt belegt, das System hat insesamt 16GB an RAM als Hauptspeicher und es gibt keinen Swapspace mehr.

default overcommit

Das ist der erste Testfall, der normale Zustand von overcommit_memory=0. Da hier zu erwarten ist, dass wir mehr Speicher bekommen werden, als wir tatsälich haben, ist die Ausgabe per tail auf die letzten 10 Zeilen beschränkt:

$ cat /proc/sys/vm/overcommit_memory 
0
$ ./realloc |tail
allocated: 100608 MB
allocated: 100864 MB
allocated: 101120 MB
allocated: 101376 MB
allocated: 101632 MB
allocated: 101888 MB
allocated: 102144 MB
allocated: 102400 MB
allocated: 102656 MB
100 GB of RAM allocated, stopping

Siehe da, wir können problemlos 100 GB RAM anfordern und es ginge noch weit mehr, würden wir hier nicht abbrechen.

full overcommit

Hier ist das gleiche Verhalten zu erwarten, warum sollte hier auch weniger RAM vergeben werden?

$ cat /proc/sys/vm/overcommit_memory
1
$ ./realloc |tail
allocated: 100608 MB
allocated: 100864 MB
allocated: 101120 MB
allocated: 101376 MB
allocated: 101632 MB
allocated: 101888 MB
allocated: 102144 MB
allocated: 102400 MB
allocated: 102656 MB
100 GB of RAM allocated, stopping

Das war nun wenig überraschend.

no overcommit

Was passiert nun, wenn wir gar keien overcommit zulassen?

$ cat /proc/sys/vm/overcommit_memory
2
$ ./realloc |tail
allocated: 1536 MB
allocated: 1792 MB
allocated: 2048 MB
allocated: 2304 MB
allocated: 2560 MB
allocated: 2816 MB
allocated: 3072 MB
allocated: 3328 MB
allocated: 3584 MB
failed to allocate 3758096384 memory

Voila, das Programm bricht sauber ab. Warum wir hier nicht die vollen freien Bytes bekommen, liegt in der obigen Aussage. Per default wird nur Swap + 50% RAM vergeben. Swap haben wir nicht mehr und zu dem Zeitpunkt waren vermutlich nur etwas über 7 GB RAM aktuell frei.

Abgewandelter Test

Offenbar verhält sich das heuristic wie völliger overcommit. Es sollte doch hier einen Unterschied geben, oder?

Wie wäre es also, wenn wir obiges Testprogramm anpassen und den zugewiesenen Speicher auch einfach einmal benutzen?

Dazu passen wir einfach den obigen Code an und fügen nach der Abfrage ob ein NULL-Pointer zurückgegeben wurde, ein

 // use the memory
 memset(buf,0,count*MB);

hinzu. Dieser einfach Befehl schreibt einfach nur Nullen in den zugewiesenen Speicherbereich. Das Programm nennen wir nun realloc2.

Dann schauen wir einmal, was jetzt passiert…

default overcommit

Da wir den Speicher auch gleich noch befüllen, dauert die Ausführung des Programmes nun deutlich länger.

Jetzt ist erst einmal wieder die heuristic gefragt:

$ cat /proc/sys/vm/overcommit_memory
0
$ ./realloc2  |tail
allocated: 12032 MB
allocated: 12288 MB
allocated: 12544 MB
allocated: 12800 MB
allocated: 13056 MB
allocated: 13312 MB
allocated: 13568 MB
allocated: 13824 MB
allocated: 14080 MB
failed to allocate 14763950080 memory

Aha, nun ist bei etwas unter 14 GB Schluss. Denn mehr Speicher kann das System auch nicht vergeben:

$ free -h
         total        used        free      shared  buff/cache available
Mem:      15Gi       1,5Gi        13Gi       251Mi       363Mi      13Gi
Swap:       0B          0B          0B

Das ist also durchaus sinnvoll!

no overcommit

Was passiert aber im Fall von völligem overcommit?

Das ist nun der interessante Teil:

$ cat /proc/sys/vm/overcommit_memory
1
$ ./realloc2  |tail
$ echo $?
0

Nun was ist das? Das Programm liefert gar keine Ausgabe, der Rückgabewert ist aber ok?

Die Antwort liefert nun dmesg:

dmesg |tail -3
[149965.615871] Out of memory: Kill process 13185 (realloc2) score 879 or sacrifice child
[149965.615876] Killed process 13185 (realloc2) total-vm:14420200kB, anon-rss:14316496kB, file-rss:4kB, shmem-rss:0kB
[149965.766914] oom_reaper: reaped process 13185 (realloc2), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Da hat der Out of memory Killer vom Kernel zugeschlagen! Das ist nun völlig fatal, das Programm könnte gar nicht auf den fehlenden Speicher reagieren.

no overcommit

Wie sieht das nun aus, wenn es gar keinen overcommit gibt?

Genau, es ist das Gleiche wie im ersten Fall ohne den memset()-Aufruf:

$ cat /proc/sys/vm/overcommit_memory
2
$ ./realloc2  |tail
allocated: 1536 MB
allocated: 1792 MB
allocated: 2048 MB
allocated: 2304 MB
allocated: 2560 MB
allocated: 2816 MB
allocated: 3072 MB
allocated: 3328 MB
allocated: 3584 MB
failed to allocate 3758096384 memory

Ist dann der default ok?

Darüber lässt sich nun streiten. Letztendlich ist das nur ein Kompromiss zu dem unsäglichen Speicheranforderungsverhalten diverser Programme. Dabei wird Speicher so lange per overcommit zugewiesen, wie dieser auch nicht benutzt wird. Daher gab es im ersten Fall die 100 GB an zugewiesenen Speicher, obwohl dieser definitiv nicht im System verfügbar ist, es gab aber noch genug freie Reserven.

Wenn wir jedoch den Speicher auch gleich belegen, dann reduziert sich der freie Pool und irgendwann vergibt dann auch der Kernel keinen Speicher mehr.

Also ist doch alles gut?

Das ist leider nicht ganz so einfach: Was passiert, wenn mehrere Programme Speicher allokieren und dann vielleicht ein wenig später erst nutzen? Dann würden sie erst aus dem freien Pool viel Speicher per overcommit zugewiesen bekommen. Wenn sie ihn dann aber verwenden, kann es sein, dass die anderen Programme schon den letzten freien Speicher verwendet, also belegt haben.

Simulieren kann man das mit obigem Programm auch. Man müsste es entsprechend anpassen oder einfach mehrfach zeitversetzt starten. Dann tritt, wie es zu erwarten war, wieder das Problem mit dem fehlenden Speicher bei völligem overcommit auf:

$ ./realloc2 |tail
$ echo $?
0
$ dmesg |tail -3
[155595.704888] Out of memory: Kill process 24296 (realloc2) score 696 or sacrifice child
[155595.704892] Killed process 24296 (realloc2) total-vm:11536616kB, anon-rss:11338296kB, file-rss:4kB, shmem-rss:0kB
[155595.876869] oom_reaper: reaped process 24296 (realloc2), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Hier wird halt die Folge des Kompromisses offenbar: Es kann dennoch zu plötzlichem, eigentlich unerklärlichem Absturz eines Programmes kommen. Das ist für ein Desktop-System oder Notebook in aller Regel noch tolerabel, für einen Server jedoch durchaus fatal.

Und nein, automatische Neustarts durch systemd sind hier keine adäquate Lösung! Ein Administrator sollte wissen, wann ein Dienst warum beendet wird. Nur so kann auch geeignet darauf reagiert werden.

Gute Programme würden eventuell darauf reagieren, wenn der Speicher knapper wird und dann zum Beispiel einige Dienste reduzieren, keine neuen Verbindungen annehmen bis wieder Speicher da ist oder ähnliches. Wenn aber der OOM-Killer zuschlägt, hat ein Programm keine Chance und der Entwickler könnte das Problem noch nicht einmal lokalisieren oder beheben: Es liegt nicht mehr in seiner Macht.

Weitere Einstellungen für overcommit

Wenn wir uns schon overcommit ansehen, dann sollten wir auch die weiteren Optionen dazu ansehen. Die sind mitunter auch relevant um ein System optimal nutzen zu können.

admin_reserve_kbytes

Über diesen Wert kann dem Administrator noch ein Rest an Speicher freigehalten werden, so dass er sich zum Beispiel per ssh einwählen und wildgewordene Prozesse beenden kann.

Der default-Wert hierfür sind der Minimum von 3% der freien Seiten oder 8 kB. Das ist aber nur sinnvoll, für overcommit_memory=0. Wird kein overcommit erlaubt, so muss der gesamte virtuelle Speicher für die zu startenden Prozesse (also zum Beispiel ssh und bash) vorhanden sein, der Wert sollte also größer sein. Die Kerneldokumentation empfiehlt hier dann 128 MB.

overcommit_kbytes und overcommit_ratio

Diese beiden Werte schließen sich gegenseitig aus, wird einer aktiviert, so wird der andere deaktiviert. Sie greifen auch nur, wenn overcommit_memory=2 gesetzt ist.

Dann dürfen Prozesse nicht mehr Speicher allokieren als Swap-Space plus den angegebenen Wert an RAM. Im ersten Fall, also bei overcommit_kbytes sind es die absoluten Werte, im zweiten Fall ist es der Prozentteil des RAMs. Dieser Wert steht per default auf 50, damit sind maximal Swap-Sapce plus 50% des RAMs für einen Prozess nutzbar.

Wenn kein Swapspeicher vorhanden ist, bedeutet dass, das ein Prozess in dem Fall nie mehr als 50% des verfügbaren RAMs allokieren kann. Das sollte einem durchaus bewusst sein und mitunter sollte der Wert dann entsprechend angepasst werden!

Fazit

Es ist auf der einen Seite recht traurig, dass viele Programmierer nicht darauf achten, wieviel Speicher sie wirklich benötigen. Mitunter greifen sie auch auf Bibliotheken zu, die sich ebenso verhalten, weil ein anderer Programmierer zu bequem war, nachzudenken.

Das führte dann dazu, dass overcommit_memory eingeführt wurde und viele Programme laufen damit wunderbar obwohl sie mehr Speicher belegen wollen, als das System hat.

Damit wird aber der Druck auf die Programmierer reduziert, sorgfältiger zu arbeiten. Es gibt scheinbar keine Probleme mehr.

Auf der anderen Seite wiederum, verhindert das overcommit_memory, dass ein Programm auf Speicherknappheit geeignet reagieren kann. Es wird dann mitunter einfach beendet, obwohl es fehlerfrei läuft.

Das führte dann auch zu Entwicklungen von Hilfsprogrammen wie monit oder supervisor und auch systemd bietet die Eigenschaft: Wenn ein Programm, warum auch immer, unsanft beendet wird, so wird es einfach neu gestartet.

Insgesamt gesehen, liegt das Kind schon im berühmten Brunnen und man muss damit irgendwie leben. Daher finde ich persönlich, dass die default-Einstellung von `overcommit_memory=0 auf einem Notebook oder Desktop durchaus vertretbar ist. Diese werden häufiger einmal neu gestartet oder wenn hier ein Programm bei der Arbeit crasht, dann ist es ärgerlich, kann aber leicht neu gestartet werden. Viele Programme erkennen das und bieten an, den alten Zustand wieder herzustellen.

Schwieriger sehe ich das allerdings bei Servern. Da ist das Verhalten der Programme, deren Nutzung und die Erwartung eine andere. Hier rate ich dazu, es einfach abzustellen und für genügend Swap-Speicher zu sorgen.


Previous post: Hochverfügbare Dateien mit GlusterFS