Layer 8Ehrlicher Tech-Support

PostgreSQL: work_mem erhöht, Server geht der Speicher aus

work_mem gilt nicht je Abfrage, sondern je Sortier- oder Hash-Operation; Hash-Tabellen dürfen ab Werk das Doppelte. Gesamt: ein Vielfaches.

Aufwand:
ca. 25 Min.
Voraussetzung:
psql oder ein anderer SQL-Client, Das Recht, SHOW und EXPLAIN auszuführen, und für dauerhafte Änderungen das Recht für ALTER ROLE oder ALTER DATABASE, Zugriff auf postgresql.conf oder die Konfiguration des Verbindungspools
Gilt für:
PostgreSQL 18 laut Dokumentation unter docs/current (Kapitel 19.4 Resource Consumption), abgerufen am 17.10.2026

Das Symptom

Ein Bericht sortiert eine große Tabelle und braucht Minuten. Jemand liest, dass PostgreSQL ab Werk nur vier Megabyte für Sortierungen vorsieht, setzt work_mem in der postgresql.conf auf 512MB, lädt die Konfiguration neu, und der Bericht läuft tatsächlich schneller. Zwei Tage später ist der Server zur Hauptgeschäftszeit nicht mehr zu erreichen. Im Log stehen Out-of-memory-Fehler, der Kernel hat Prozesse beendet, und der Speicherbedarf lag weit über dem, was 512MB mal der Zahl der Verbindungen ergeben hätte.

Die Rechnung work_mem mal max_connections steht in vielen Köpfen und ist leider die Untergrenze. Der Bericht war nicht das Problem. Das Problem war, dass hundert Webverbindungen mit ihren kleinen Abfragen auf einmal dieselbe Erlaubnis hatten.

Die „Layer 8“-Wahrheit

work_mem ist kein Deckel pro Abfrage und erst recht keiner pro Verbindung. Die Dokumentation beschreibt ihn als Grundwert für eine einzelne Operation: „Sets the base maximum amount of memory to be used by a query operation (such as a sort or hash table) before writing to temporary disk files.“ Ohne Einheit wird der Wert als Kilobyte gelesen, die Voreinstellung sind vier Megabyte. Eine Abfrage mit ORDER BY, DISTINCT und zwei Hash-Joins hat vier solche Operationen, und jede darf für sich bis an die Grenze gehen.

Die Dokumentation sagt das nicht beiläufig, sondern als Warnung: Eine komplexe Abfrage könne mehrere Sortier- und Hash-Operationen gleichzeitig ausführen, mehrere Sitzungen könnten das gleichzeitig tun, und: „Therefore, the total memory used could be many times the value of work_mem; it is necessary to keep this fact in mind when choosing the value.“ Wer das Vielfache mit der Verbindungszahl verwechselt, hat einen Faktor vergessen, und wer dann noch Hash-Tabellen übersieht, einen zweiten. Denn für hash-basierte Operationen gilt nicht work_mem, sondern work_mem mal hash_mem_multiplier, und dieser Faktor steht ab Werk auf 2,0. Aus 512MB werden damit für einen Hash-Join ein Gigabyte, pro Operation, pro Sitzung.

Der Fehler liegt also nicht im Erhöhen, sondern im Ort des Erhöhens. Ein Wert in der postgresql.conf gilt für jede Operation jeder Sitzung. Die Rolle der Webanwendung, die tausend kurze Abfragen pro Minute stellt, hat mit 512MB nichts anzufangen, darf sie aber beanspruchen, sobald einmal eine Abfrage ungünstig geplant wird. Der Bericht, der die Erhöhung brauchte, läuft dagegen einmal am Tag in einer Sitzung. Dort gehört der Wert hin: per SET LOCAL innerhalb der Transaktion oder per ALTER ROLE an eine eigene Berichtsrolle. Die Grundlinie für alle bleibt klein.

Zwei Nachbarn im selben Kapitel machen die Sache besser steuerbar. Wenn vor allem Hash-Joins und Aggregationen auf die Platte ausweichen, rät die Dokumentation, eher hash_mem_multiplier zu erhöhen als work_mem, gerade dann, wenn ein höheres work_mem Speicherdruck erzeugt; Werte von 2,0 bis 8,0 nennt sie für Systeme, auf denen work_mem schon bei 40MB oder mehr steht. Und wer work_mem bewusst klein lässt, verschiebt Last auf temporäre Dateien; temp_file_limit begrenzt deren Platz pro Prozess und bricht die Transaktion ab, die ihn überschreitet, statt den Datenträger zu füllen. Ab Werk steht dort -1, also keine Grenze.

Am Ende ist die Abwägung dieselbe wie bei jedem geteilten Vorrat: Nicht die größte Abfrage bestimmt den sicheren Wert, sondern die Zahl derer, die ihn gleichzeitig ausschöpfen dürfen. EXPLAIN (ANALYZE, BUFFERS) zeigt, ob eine Abfrage überhaupt auf die Platte schreibt. Oft tut sie es, weil ein Index fehlt, und dann verschwindet die Sortierung ganz, statt größer zu werden.

Die echte, verifizierte Lösung

1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:

  1. 1.

    Den wirksamen Wert und seine Herkunft lesen

    Zuerst in der betroffenen Sitzung nachsehen, nicht im psql des Administrators, denn der Wert kann je Rolle, je Datenbank oder vom Pool gesetzt sein. SHOW work_mem und SHOW hash_mem_multiplier zeigen, was gerade gilt. pg_settings nennt dazu in der Spalte source, woher der Wert stammt, etwa configuration file, user oder session. Steht dort ein großer Wert für die Rolle der Webanwendung, ist die Ursache für Speicherdruck gefunden, bevor irgendeine Abfrage angesehen wurde.

    SHOW work_mem;
    SHOW hash_mem_multiplier;
    SELECT name, setting, unit, source
      FROM pg_settings
     WHERE name IN ('work_mem', 'hash_mem_multiplier', 'temp_file_limit');
    
  2. 2.

    Mit EXPLAIN ANALYZE prüfen, ob überhaupt auf die Platte geschrieben wird

    Bevor man den Wert anhebt, lohnt der Blick, ob die fragliche Abfrage wirklich auf temporäre Dateien ausweicht. EXPLAIN (ANALYZE, BUFFERS) zeigt je Knoten, ob eine Sortierung im Speicher oder auf der Platte lief und wie viele temporäre Blöcke gelesen und geschrieben wurden. Nur wenn dort tatsächlich externe Sortierungen oder Batches bei Hash-Joins erscheinen, bringt mehr work_mem etwas. Oft fehlt stattdessen ein Index, und die Sortierung verschwindet ganz, statt größer zu werden.

    EXPLAIN (ANALYZE, BUFFERS)
    SELECT ...;
    
  3. 3.

    Den Wert dort anheben, wo er gebraucht wird: Sitzung oder Rolle

    Für den einzelnen Bericht reicht SET LOCAL innerhalb der Transaktion; nach COMMIT gilt wieder die Vorgabe. Für einen Berichtsdienst, der regelmäßig große Sortierungen fährt, ist eine eigene Rolle mit eigenem Wert die saubere Lösung. So bekommt die Operation, die 256MB braucht, ihre 256MB, während die Anwendungsrolle bei einem kleinen Wert bleibt und hundert gleichzeitige Verbindungen den Server nicht an die Wand fahren. Der Wert in postgresql.conf bleibt die Grundlinie für alle, nicht die Ausnahme für einen.

    BEGIN;
    SET LOCAL work_mem = '256MB';
    -- Berichtsabfrage hier
    COMMIT;
    
    ALTER ROLE berichte SET work_mem = '256MB';
    
  4. 4.

    Bei Hash-Joins zuerst hash_mem_multiplier erwägen, nicht work_mem

    Weicht vor allem bei Hash-Joins und Aggregationen etwas auf die Platte aus, empfiehlt die Dokumentation, den Faktor hash_mem_multiplier zu erhöhen statt work_mem, besonders wenn das Anheben von work_mem Speicherdruck erzeugt. Der Faktor wirkt nur auf hash-basierte Operationen; Sortierungen bleiben bei ihrem Grundwert. Werte zwischen 2,0 und 8,0 nennt die Dokumentation für Umgebungen, in denen work_mem bereits bei 40MB oder mehr liegt. Das ist die feinere Stellschraube, die nicht jeden ORDER BY mitvergrößert.

    SET hash_mem_multiplier = 4.0;
    
  5. 5.

    Eine Obergrenze für temporäre Dateien setzen

    Wer work_mem bewusst klein hält, verschiebt die Last auf temporäre Dateien. Damit eine einzelne entgleiste Abfrage nicht den Datenträger füllt, begrenzt temp_file_limit den Platz, den ein Prozess dafür gleichzeitig belegen darf; die Transaktion wird beim Überschreiten abgebrochen. Ab Werk steht der Wert auf -1, also ohne Grenze. Nur Superuser und Rollen mit dem entsprechenden SET-Recht dürfen ihn laut Dokumentation ändern. Der Wert gehört deshalb in postgresql.conf oder an die Rolle, nicht in die Anwendung.

    ALTER SYSTEM SET temp_file_limit = '10GB';
    SELECT pg_reload_conf();
    

Häufige Fragen (FAQ)

?Wofür gilt work_mem genau?

Für eine einzelne Operation innerhalb einer Abfrage, nicht für die Abfrage als Ganzes. Die Dokumentation beschreibt den Parameter so: „Sets the base maximum amount of memory to be used by a query operation (such as a sort or hash table) before writing to temporary disk files.“ Ohne Einheit gilt der Wert als Kilobyte, die Voreinstellung sind vier Megabyte. Sortieroperationen entstehen laut Dokumentation bei ORDER BY, DISTINCT und Merge-Joins, Hash-Tabellen bei Hash-Joins, hash-basierter Aggregation, Memoize-Knoten und IN-Unterabfragen. Eine Abfrage mit zwei Sortierungen und einem Hash-Join hat damit drei Operationen, die jede für sich bis an die Grenze gehen dürfen.

?Warum braucht der Server mehr Speicher, als work_mem mal Verbindungen ergibt?

Weil die Rechnung schon in der Dokumentation anders steht. Sie warnt ausdrücklich: „Therefore, the total memory used could be many times the value of work_mem; it is necessary to keep this fact in mind when choosing the value.“ Vorher erklärt sie, dass eine komplexe Abfrage mehrere Sortier- und Hash-Operationen gleichzeitig ausführen kann und dass mehrere Sitzungen solche Operationen gleichzeitig laufen lassen. Dazu kommt, dass Hash-Tabellen nicht work_mem als Grenze haben, sondern work_mem mal hash_mem_multiplier, ab Werk also das Doppelte. Wer 256MB mal 100 Verbindungen rechnet, hat die Untergrenze berechnet, nicht die Obergrenze.

?Was ist hash_mem_multiplier und wann hilft er?

Ein Faktor, der nur auf hash-basierte Operationen wirkt. Die Dokumentation: „The final limit is determined by multiplying work_mem by hash_mem_multiplier. The default value is 2.0, which makes hash-based operations use twice the usual work_mem base amount.“ Sie empfiehlt, den Faktor dort anzuheben, wo Operationen regelmäßig auf die Platte ausweichen und das bloße Erhöhen von work_mem Speicherdruck erzeugt, der sich laut Dokumentation typischerweise als zeitweilige Out-of-memory-Fehler zeigt. Höhere Werte im Bereich 2,0 bis 8,0 oder mehr nennt sie für Umgebungen, in denen work_mem schon auf 40MB oder mehr steht. Der Faktor erlaubt also, Hash-Joins gezielt zu begünstigen, ohne jede Sortierung mitzuvergrößern.

?Kann ich work_mem nur für den Bericht anheben, der es braucht?

Ja, und das ist der empfohlene Weg, weil der Wert pro Sitzung und pro Rolle gesetzt werden kann. Ein SET work_mem in der Sitzung oder ein SET LOCAL innerhalb der Transaktion hebt den Wert nur dort an, wo die große Sortierung tatsächlich läuft; die Rolle der Webanwendung behält ihren kleinen Wert. Dauerhaft lässt sich das mit ALTER ROLE für eine eigene Berichtsrolle hinterlegen. Der Server-weite Wert in postgresql.conf gilt dagegen für jede Operation jeder Sitzung, also auch für die hundert kurzen Abfragen pro Sekunde, die vier Megabyte nie ausnutzen würden.

?Was begrenzt den Platz der temporären Dateien, wenn work_mem nicht reicht?

temp_file_limit. Die Dokumentation: „Specifies the maximum amount of disk space that a process can use for temporary files, such as sort and hash temporary files, or the storage file for a held cursor. A transaction attempting to exceed this limit will be canceled.“ Die Voreinstellung -1 bedeutet keine Grenze; ohne Einheit gilt der Wert als Kilobyte. Die Grenze bezieht sich laut Dokumentation auf den Gesamtplatz, den alle temporären Dateien eines PostgreSQL-Prozesses zu einem Zeitpunkt belegen, ausdrückliche temporäre Tabellen zählen nicht mit. Wer work_mem klein lässt und die Platte voll läuft, hat hier den Schalter, der eine entgleiste Abfrage abbricht, statt den Datenträger zu füllen.

Belege

Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.

Zuletzt geprüft am . Dokumentation altert – ein Datum daneben ist der einzige ehrliche Umgang damit.

Auch zu finden unter

Verwandte Fehler

SWLästigconnection refused

PostgreSQL: Connection refused auf Port 5432

Die App erreicht die Datenbank nicht — Connection refused? Meist lauscht PostgreSQL nur auf localhost oder die Client-Freigabe in pg_hba.conf fehlt.

SWLästigpassword authentication failed for user

PostgreSQL: password authentication failed for user

PostgreSQL meldet „password authentication failed“? Falsches Passwort, falscher Nutzer oder pg_hba.conf erlaubt die Methode nicht. So kommst du rein.

SWLästigsorry, too many clients already

PostgreSQL: too many clients already

Die Datenbank weist neue Verbindungen ab? Das Verbindungslimit ist erreicht — meist, weil eine App Verbindungen nicht schließt oder ein Pool fehlt.