Wie man Speicherlecks in Node.js in einer Produktionsumgebung erkennt
Was ist ein Speicherleck?
Ein Speicherleck in Node.js entsteht, wenn ein Programm Referenzen auf Objekte speichert, die es nicht mehr benötigt. Dadurch kann der Garbage Collector den Speicher dieser Objekte nicht freigeben. Werden Speicherlecks nicht behoben, steigt der Speicherverbrauch an, was die Anwendung verlangsamen oder sogar zum Absturz bringen kann.

Mögliche Gründe
Kreisförmige Referenzen
Zirkuläre Abhängigkeiten zwischen Objekten können die Fähigkeit des Garbage Collectors beeinträchtigen, die Objekte zu erkennen und zu sammeln. Wenn zwei Objekte aufeinander verweisen und nicht aus einem Node.js-Modul exportiert werden, sodass eine externe Referenz bestehen kann, besteht die Gefahr, dass die Objekte nicht eingesammelt werden.
Globale Variablen
Globale Variablen in Node.js sind die Variablen auf die der Root Node verweist. Diese Variablen werden während der Ausführungszeit einer Anwendung niemals vom Garbage Collector entfernt. Sie belegen Speicher solange die Anwendung läuft.
Hier ist ein Beispiel:
// Example of a potential memory leak with global variables
const globalData = [];
function addDataToGlobal() {
// Adding data to the global array without clearing or managing it.
globalData.push(new Array(1000000).fill('data'));
}
Schließungen (Closures)
Schließungen behalten Informationen über ihren Kontext. Wenn eine Schließung (Closure) eine Referenz auf ein großes Objekt im Heap hält, wird das Objekt so lange im Speicher gehalten, wie die Schließung aktiv ist. Dies kann zu einem Speicherleck führen, wenn die Schließung nicht ordnungsgemäß verwendet wird.
Hier ist ein Beispiel:
// Example of a potential memory leak with closures
function createClosure() {
const data = 'Sensitive data';
return function() {
console.log(data);
};
}
const closure = createClosure();
// The 'data' variable is retained in the closure, preventing it from being garbage collected.
Wie man Speicherlecks in einer Produktionsumgebung identifiziert?
Es ist wichtig, ein Tool zur Überwachung der Anwendungsleistung zu nutzen, insbesondere da der Zugriff auf die Produktionsumgebung oft eingeschränkt ist. New Relic ist ein leistungsstarkes Tool, das uns ermöglicht verschiedene Aspekte unserer Anwendung zu überwachen. Das untenstehende Diagramm zeigt beispielsweise die Speicherauslastung im Zeitverlauf.

Wie man Speicherlecks behebt
Der erste Schritt zur Behebung von Speicherlecks ist die Produktionsumgebung so genau wie möglich zu replizieren. Docker-Container sind hierbei hilfreich, da sie die Spezifikation des Betriebssystems, der Versionen und anderer wichtiger Details festlegen können.
Um die Node.js-Anwendung im Debug-Modus auszuführen, gehe wie folgt vor:
Aktualisiere den Startprozess in deinem Dockerfile.
Füge das Flag
--inspect
hinzu, um den Speicher zu überwachen.
...
EXPOSE 9229 ## <--debug port
...
CMD ["node", "--inspect-brk=0.0.0.0:9229", "./src/main.js"]
Öffne die Chrome DevTools: Wir können die dedizierten DevTools für Node von Chrome für die Speicherdiagnose verwenden. Kopiere und füge den folgenden Link in Google Chrome ein: chrome://inspect/#devicesund folgen Sie dann dem Link.
Starte den Docker-Container: Wenn der Docker-Container gestartet ist, sollten die DevTools automatisch eine Verbindung zu unserer Anwendung herstellen.
Vergleichen von Snapshots
Wenn du das Speicherleck reproduzieren kannst, während deine Anwendung lokal ausgeführt wird, ist es einfacher nützliche Laufzeitinformationen abzurufen und den Fehler nachzuvollziehen. Mit dem Heap-Snapshot-Dienstprogramm kannst du mehrere Zustände deiner Anwendung vergleichen.
Wähle „Heap Snapshot“ aus und klicke auf die Schaltfläche „Snapshot erstellen“.

Beim Betrachten des aufgenommenen Snapshots kannst du jede Datenstruktur sehen, die in deinem Node-Prozess allokiert ist. Das kann jedoch etwas überwältigend sein. Um besser zu erkennen, welche Elemente Speicherlecks verursachen, solltest du weitere Snapshots aufnehmen, um sie vergleichen zu können. Beachte dabei, dass du das Leck nur beobachten kannst wenn Datenverkehr an den Server gesendet wird.
Nimm nach einer wichtigen Traffic-Anfrage an den Server einen weiteren Heap-Snapshot auf.

Wähle dann den letzten Snapshot aus und wechsle von der Ansicht "Zusammenfassung" zur Ansicht "Vergleich" im Dropdown-Menü.

Dies wird die Anzahl der angezeigten Objekte erheblich reduzieren. Eine gute Faustregel ist, zuerst die Elemente in Klammern zu ignorieren, da es sich dabei um integrierte Strukturen handelt. Das nächste Element in der Liste ist in diesem Fall "Object". Wenn du dir einige der beibehaltenen Objekte ansiehst, kannst du Beispiele für die durchgesickerten Daten erkennen, die dir helfen das Leck in deiner Anwendung nachzuvollziehen.