Node.js OOMKilled: Wie man das tatsächliche Speicherproblem findet_
Was tatsächlich dazu führt, dass Node.js-Dienste OOMKilled werden, wie man den Heap inspiziert und wann das Erhöhen des Speicherlimits hilft, anstatt das Leck zu verbergen.
Wenn ein Node.js-Dienst in Kubernetes OOMKilled wird, ist die Ursache nicht immer "das Pod benötigt mehr Speicher" und es ist nicht immer "V8 hat geleakt".
Sie haben mindestens drei bewegliche Teile:
- den V8-Heap
- Speicher außerhalb des V8-Heaps, wie native Puffer
- das vom Laufzeitumgebung durchgesetzte Speicherlimit für Container
Wenn Sie diese nicht trennen, können Sie Tage damit verbringen, das falsche Problem zu beheben.
## Beginnen Sie mit dem richtigen mentalen Modell
`process.memoryUsage()` berichtet über mehrere Speicherbereiche:
```js
const usage = process.memoryUsage();
console.log({
rss: usage.rss,
heapTotal: usage.heapTotal,
heapUsed: usage.heapUsed,
external: usage.external,
arrayBuffers: usage.arrayBuffers,
});
Die wichtige Unterscheidung ist:
heapUsedist der von V8 verwaltete JavaScript-ObjekteexternalundarrayBuffersrepräsentieren oft Speicher, der noch gegen das Containerlimit zähltrssist der resident set des Prozesses und ist in der Regel die Zahl, um die sich die Operations-Teams während Vorfällen kümmern
Sie können einen gesund aussehenden Heap haben und dennoch getötet werden, weil der native Speicher oder die Puffer weiter wachsen.
--max-old-space-size ist ein Werkzeug, kein Diagnoseinstrument
Dieses Flag erhöht die Größe des alten Generationheaps von V8:
node --max-old-space-size=4096 server.js
Das kann die richtige Lösung sein, wenn:
- der Dienst tatsächlich mehr lebenden Heap benötigt
- die Garbage Collection gesund ist
- das Containerlimit genügend Spielraum hat
Es ist die falsche Lösung, wenn:
- das Speicherleck in Benutzerobjekten ist, die sammelbar sein sollten
- das Speichermanagement in Puffern oder nativen Add-ons liegt
- das Pod bereits zu nah am Containerlimit ist
Das Erhöhen des Heap-Limits bei einem undichten Prozess verzögert lediglich den Absturz.
Häufige Ursachen für Speicherwachstum
In Produktions-Node-Diensten überprüfe ich normalerweise zuerst diese:
- Langlebige Maps oder Caches ohne Eviktion
- Ereignislistener, die wiederholt angebracht und nie entfernt werden
- Warteschlangen, die Arbeit schneller annehmen als die Arbeiter sie abarbeiten können
- Große JSON-Payloads, die vollständig im Speicher gepuffert werden
- Streams, die durch
await response.json()ersetzt wurden - Native Add-ons oder Bild-/Videobearbeitungsbibliotheken, die Speicher außerhalb des Heaps halten
Keine dieser Ursachen ist exotisch. Sie sind normale Ingenieurfehler unter Last.
Heap-Snapshots sind den Aufwand wert
Wenn der Verdacht auf Heap-Wachstum besteht, nehmen Sie einen Snapshot und inspizieren Sie die retained size:
import { writeHeapSnapshot } from "node:v8";
const filename = writeHeapSnapshot();
console.log(`Heap-Snapshot in ${filename} geschrieben`);
Öffnen Sie dann den Snapshot in den Chrome DevTools und suchen Sie nach:
- großen Erhaltungswegen
- unerwartet großen Arrays oder Maps
- duplizierten Objekten, die kurze Lebensdauern haben sollten
- Closures, die anfrage-spezifischen Zustand beibehalten
Die Frage ist nicht "Welches Objekt ist groß?" Es ist "Warum ist dieses Objekt noch erreichbar?"
Lecks verstecken sich oft im Bequemlichkeitscode
Dieses Muster ist gefährlicher, als es aussieht:
const pending = new Map<string, RequestContext>();
export function trackRequest(id: string, ctx: RequestContext) {
pending.set(id, ctx);
}
Ohne einen klaren Löschpfad wird diese Map zu einer unbeabsichtigten In-Memory-Datenbank.
Die Lösung ist in der Regel nicht clever. Es ist Disziplin im Lebenszyklus:
- Einträge entfernen, wenn die Arbeit abgeschlossen ist
- Caches begrenzen
- große Payloads streamen
- Vorzugsweise Rückstau statt alles puffernd
Container ändern den Fehlermodus
Innerhalb von Kubernetes konkurriert der Prozess mit dem Containerlimit, nicht nur mit den V8-Standardeinstellungen.
Das bedeutet:
- beobachten Sie
rss, nicht nur den Heap - lassen Sie Spielraum für native Zuweisungen
- vermeiden Sie es,
--max-old-space-sizenahe am Containerlimit zu setzen
Ein Prozess mit einem 4 GB Heap in einem 4 GB Container ist nicht "effizient". Er ist fragil.
Ein praktischer Vorfallprozess
Wenn ein Node-Dienst OOMKilled wird:
- Grafiken
rss,heapUsedund Anfragenvolumen zusammen erstellen - Überprüfen, ob das Wachstum nach dem Rückgang des Verkehrs zurückgesetzt wird
- Speicherintensive Code-Pfade und die Verarbeitung großer Payloads überprüfen
- Einen Heap-Snapshot erfassen, wenn das Heap-Wachstum verdächtig aussieht
- Nur dann entscheiden, ob eine Heap-Anpassung gerechtfertigt ist
Diese Reihenfolge ist wichtig. Das Tuning vor dem Verständnis führt in der Regel zu einem langsameren Vorfall, nicht zu einem besseren System.
Weiterführende Literatur
Verwandte Artikel.
Weiterlesen mit eng verwandten Artikeln zu Software Engineering, Architektur und Implementierungs-Trade-offs.
Node.js friert weiterhin ein, wenn Sie CPU-Arbeit im Haupt-Thread ausführen
Node ist hervorragend im nicht blockierenden I/O, aber synchrone CPU-intensive Operationen blockieren weiterhin den Ereignis-Loop. Diese Unterscheidung ist der Ursprung vieler Vorfälle in der Produktion.
So finden Sie Speicherlecks in Node.js mit Heap-Snapshots
Heap-Snapshots, behaltener Speicher und die Event-Listener-Muster, die alte Anfragen lange nach ihrem garbage collection am Leben erhalten.