Technik

Wenn ein Node.js-Server heimlich RAM frisst

Memory Leaks in Node.js sind kein exotischer Sonderfall. Sie gehören zu den Fehlern, die im Alltag gern zu spät auffallen: Der Server läuft, Antworten kommen noch durch, aber der RAM-Verbrauch klettert Stunde für Stunde nach oben. Irgendwann greift der OOM-Killer, der Container startet neu oder das System wird schlicht teuer.

Dass das Thema gerade wieder Aufmerksamkeit bekommt, ist nachvollziehbar. Viele Teams betreiben kleine bis mittlere Node-Dienste mit dem Gefühl, das Ganze sei leichtgewichtig. Das stimmt oft nur so lange, bis im Live-Betrieb Objekte im Speicher hängenbleiben, die nie wieder freigegeben werden.

Das Tückische an Leaks in Node.js

Ein Speicherleck ist in Node.js meist kein spektakulärer Totalausfall, sondern ein schleichender Defekt. Die Garbage Collection arbeitet, aber bestimmte Objekte bleiben referenziert. Genau das macht die Suche mühsam: Der Prozess lebt weiter, Logs sehen normal aus, nur die Speichernutzung kennt eine Richtung.

Gerade in Backend-Systemen sind die üblichen Verdächtigen bekannt: globale Caches ohne Limit, Event-Listener, die nicht aufgeräumt werden, Request-Kontext, der länger lebt als gedacht, und Session-Speicher, der für Entwicklung okay ist, im Produktivbetrieb aber zur Falle wird.

Ein Punkt sticht heraus: Der eingebaute MemoryStore von Express-Session ist ausdrücklich nicht für Produktion gedacht und leakt unter typischen Bedingungen Speicher. Wer so etwas im Stack übersieht, handelt sich einen Fehler ein, der sich erst unter Last sauber zeigt.

Warum Heap Dumps in der Praxis oft der Wendepunkt sind

Bei akuten Verdachtsfällen führt an Heap Dumps meist kein Weg vorbei. Sie zeigen nicht nur, dass Speicher wächst. Sie zeigen, was wächst. Genau dieser Unterschied entscheidet, ob man tagelang im Nebel stochert oder den Verursacher sauber eingrenzt.

Der praktische Ablauf ist bekannt: Speicheranstieg beobachten, zu mehreren Zeitpunkten Dumps ziehen, in den DevTools analysieren und nach Objekten suchen, die zwischen den Snapshots anwachsen. Das ist keine elegante Kür, sondern oft die einzige Methode, die aus einem diffusen Produktionsproblem einen konkreten Bug macht.

Dabei hilft es, wenn Anwendungen bereits Telemetrie liefern. Wer Laufzeiten, Request-Muster und Speichernutzung sichtbar macht, erkennt schneller, ob ein Leak an Traffic-Spitzen hängt, an einzelnen Endpunkten oder an Hintergrundjobs. Ein Server-Timing-Header kann bei Latenzfragen nützlich sein. Für Leaks ersetzt er aber keine Heap-Analyse.

Der eigentliche Schaden ist oft wirtschaftlich

Ein Memory Leak ist nicht bloß ein Technikproblem. Er frisst Budget. Wenn Node-Instanzen wegen steigender Speichernutzung immer größer dimensioniert werden müssen, kippt das Versprechen der schlanken Laufzeit schnell. Aus einem kleinen Service wird dann ein Container mit viel zu viel RAM-Reserve, nur um den Absturz hinauszuzögern.

In Kubernetes-Umgebungen fällt das besonders hart auf. Dort schlagen Speichergrenzen direkt auf Stabilität und Autoscaling durch. Pods werden beendet, neu gestartet und verteilen den Fehler nur weiter. Das kaschiert die Ursache kurzfristig, verschiebt das Problem aber bloß in die Infrastrukturkosten.

Was Teams daraus mitnehmen sollten

Die wichtigste Lehre ist banal und wird trotzdem oft ignoriert: Wenn ein Node-Service im Betrieb stetig mehr RAM braucht, ist das kein normaler Wachstumsverlauf. Man sollte nicht zuerst die Limits erhöhen, sondern die Speicherbelegung erklären können.

Dazu gehören ein paar nüchterne Regeln: Session-Storage für Produktion bewusst wählen. Caches begrenzen. Listener und Timer prüfen. Heap Dumps als Standardwerkzeug behandeln, nicht als Notmaßnahme für besonders schlimme Wochen. Und vor allem: den Live-Betrieb nicht mit der lokalen Entwicklung verwechseln. Viele Leaks zeigen sich erst unter realen Lastmustern.

Node.js hat hier kein grundsätzliches Architekturproblem. Aber die Plattform verzeiht bequeme Abkürzungen im Server-Code nur eine Zeit lang. Danach meldet sich der Speicher. Meist nachts. Und meist teuer.