Speicherleck

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
chwin
User
Beiträge: 16
Registriert: Freitag 4. Juli 2014, 15:07

Hallo

ich habe ein inzwischen recht großes Skript, welches AMQP Nachrichten auswertet und daruber in einer SQLite DB Buch führt.
Bei meinen Tests in der Virtuellen umgebung läuft auch alles super.

Jetzt bin ich aber in die realwelt gewechselt und musste feststellen, dass das Skript irgendwie ein Speicherleck hat, dass ständig größer wird. Will damit sagen dass der Speicherverbrauch über die zeit mehr als linear steigt.
DAs passiert aber nur unter hoher Last. Wie ist das Möglich?
Solange das Skript mit 20% CPU läuft ist alles OK (Normales Datenaufkommen. Wenn ich jedoch das Skript ne Zeit lang aus hatte und dann die Millionen an Nachrichten abarbeiten muss steigt der Speicherbedarf am, bis der Kernel das Skript killt.
Unter Volllast dauert es etwas 2h bis 6GB Ram voll sind. Ich arbeit eine Nachricht zur Zeit ab. Von daher sollte aus Programmsicht alles gleich bleiben, egal on die Queue voll oder leer ist.

Habt ihr da ne idee, wo ich suchen muss?


Danke


Chris
BlackJack

@chwin: Da kann man so ganz allgemein nichts sagen. Wenn es sauber programmiert ist, sollte sich die Speicherverwaltung von Python eigentlich um den Müll kümmern. Schon mal mit einem (Memory-)Profiler in Python geschaut wo der Speicher verbraucht wird, also welche Programmzeilen und/oder welcher Datentyp so viel belegt?

Wie sieht das mit der Datenbank aus? Wird regelmässig commited? Kann es sein das sich die Nachrichten irgendwo in einer Queue oder so stapeln und das Programm mit dem abarbeiten nicht hinterher kommt?
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

chwin hat geschrieben:Ich arbeit eine Nachricht zur Zeit ab. Von daher sollte aus Programmsicht alles gleich bleiben, egal on die Queue voll oder leer ist.
Irgendwo ist aber wohl doch eine Stelle, an der sich die Daten ansammeln. Vielleicht wird irgendwo ein Puffer nicht schnell genug geleert. Wie fragst du die Nachrichten ab (bitte Name der Bibliothek + Funktionsaufruf nennen). Handelt es sich um eine Single oder Multi Thread Umgebung? Unterscheidet sich der Python-Code zum Abfragen von vielen angesammelten Nachrichen im Vergleich zum "Normalbetrieb"? Könntest du vielleicht die Schritte "Daten holen", "Daten verarbeiten" und ggf "Daten speichern" voneinander trennen und somit einzeln betrachten (zwecks Eingrenzung der Fehlerquelle)? Wie schon vorgeschlagen, wäre auch der Einsatz eines Profilers hilfreich, um den Datenstau ausfindig zu machen.

Weitere Ansatzpunkte: Wie schreibst (commitest) du die Daten in die DB? Jede Nachricht einzeln? Sammelst du bis zum Erreichen einer bestimmten Menge oder nach einer bestimmten Zeit? Kommen asynchrone Abläufe in deinem Programm vor, sodass die Daten womöglich schneller gelesen werden als sie geschrieben werden?
chwin
User
Beiträge: 16
Registriert: Freitag 4. Juli 2014, 15:07

HAllo

danke erstmal für die ersten Antworten.
Die Idee mit nem memory profiler hatte ich auch schon, jedoch ist das für mich völlig neu und ich kann die Ausgaben noch nicht so recht interpretieren.
Ich bin mir auch nicht sicher welchen der vorhandenen profiler ich nehmen sollte.

Die Daten rufe ich mit AMQPStorn 2.2.0 von RabbitMQ ab.
Die Daten werden dann mit lxml 3.6.4 "entpackt" und mit meiner eigenen Logik ausgewertet.
je nach dem was in den Nachrichten steht werden Datensätze in die Datenbank (SQLite3 aus der DB-API 2.0) geschrieben. Hierfür verwende ich eine eigene queue. In dieser sammel ich eine Anzahl an Datensätzen um sie dann bei erreichen einer queue Länge alle zusammen mit einem commit in die DB zu schreiben. Die Queue länge habe ich variiert. Sie hate keinen Einfluss auf das Speicherleck, jedoch auf die Performance. Sie ist derzeit auf 100 gesetzt.

Sobald der neue Datensatz in der Queu ist hohlt sich das Programm eine neue Nachricht und bearbeitet diese.

Ein wenig threading hate ich auch mit drinnen. Ich flushe z.B. die queue alle 300 Sekunden, auch wenn nicht 100 Datensätze enthalten sind. So will ich sicherstellen das in zeiten mit geringen Datenaufkommen die Daten Zeitnah in der DB sind.

Ein zweiter thread wird alle x Sekunden gestartet und Statistische informationen auszugeben.

Beide sind aber außerhalb des eigentlichen Datenflusses.


Welchen profiler sollte ich nehmen?
Gibt es einen der mir einen Überblick über die gesamtsituation gibt? Damit ich einen Anhalt habe wo ich genauer suchen muss?
Eine übersicht wie sich der Speicherbedarf der einzelnen Klassen über die Zeit entwickelt wäre erstmal super.

Gibt es eine Statische Codeanalyse die mich auf mögliche Lecks hinweist?
PyCharm zeigt mir keine diesbezüglichen Warungen.



Gruß

chris
BlackJack

@chwin: Wie ”sauber” ist der Code denn? Keine Variablen auf Modulebene? Kein ``global``? Keine Monsterfunktionen oder Gott-Klassen die mehr Objekte am Leben erhalten als eigentlich nötig? Die Bereiche der Logik ordentlich getrennt?

Letzteres wäre interessant um das Problem nachstellen zu können wobei man Komponenten durch Dummys ersetzt. Also beispielsweise den RabbitMQ-Teil durch eine Mock-Quelle, oder die Datenbankanbindung durch etwas das einfach nichts macht (ausser vielleicht die Zeit verbrauchen, welche die reguläre Anbindung auch verbraucht). So könnte man versuchen ob/wann der übermässige Speicherverbrauch aufhört wenn man Komponenten der Reihe nach durch Dummys ersetzt.

Wie sollte eine statische Codeanalyse in diesem Fall funktionieren? Wenn Du irgendwo unabsichtlich Referenzen auf Objekte behältst die Du eigentlich nicht mehr brauchst, dann müsste so eine Analyse ja raten können ob Du sie tatsächlich noch brauchst oder nicht.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

chwin hat geschrieben:Welchen profiler sollte ich nehmen?
Gibt es einen der mir einen Überblick über die gesamtsituation gibt? Damit ich einen Anhalt habe wo ich genauer suchen muss?
Eine übersicht wie sich der Speicherbedarf der einzelnen Klassen über die Zeit entwickelt wäre erstmal super.

Gibt es eine Statische Codeanalyse die mich auf mögliche Lecks hinweist?
Du könntest den memory_profiler auf solche Situationen ansetzen. Dafür solltest Du aber schon in etwa wissen, wo Du suchen musst. Vielleicht solltest Du erst einmal mit Deinen System-Tools ansetzen um festzustellen, ob es überhaupt ein Python-Prozess ist, der klemmt. Ich hatte mal ein ähnliches Problem und ZMQ stellte sich als der Übeltäter heraus.

Eine statische Codeanalyse wird Dir bei Speicherlecks nicht unbedingt helfen: Du kannst einwandfrei sauberen Code schreiben, der gezielt Speicher frisst ... :)
chwin
User
Beiträge: 16
Registriert: Freitag 4. Juli 2014, 15:07

BlackJack hat geschrieben:@chwin: Wie ”sauber” ist der Code denn? Keine Variablen auf Modulebene? Kein ``global``? Keine Monsterfunktionen oder Gott-Klassen die mehr Objekte am Leben erhalten als eigentlich nötig? Die Bereiche der Logik ordentlich getrennt?
Puh, wenn ich das wüsste :roll:
Ich bin kein sauber ausgebildeter Programmierer. Ich hab mir das selber beigebracht durch versuch und Fehler...
Ich programmiere aber regelmäßig, nur bisher nicht so große Sachen
Variablen auf modulebene sind die die ich in eienr Kalsse in __init__ dekalriere?
Wenn ja, hab ich da sehr wenige von. Die Queue ist so eine. Sonst sind da nur Konstanten deklariert z.B. die Konfiguration.

Das ganze Skript ist in Module getrennt. Ziel war es Austauschbar zu sein. Ich wollte z.B. nur die input Klasse tauschen müssen um mit anderen Quellen zu arbeiten oder nur die DB Klasse und eine andere DB zu verwenden. Zental ist nur die Logik.

BlackJack hat geschrieben:Letzteres wäre interessant um das Problem nachstellen zu können wobei man Komponenten durch Dummys ersetzt. Also beispielsweise den RabbitMQ-Teil durch eine Mock-Quelle, oder die Datenbankanbindung durch etwas das einfach nichts macht (ausser vielleicht die Zeit verbrauchen, welche die reguläre Anbindung auch verbraucht). So könnte man versuchen ob/wann der übermässige Speicherverbrauch aufhört wenn man Komponenten der Reihe nach durch Dummys ersetzt.
Schwierig, da die Logik auch lesend auf die DB zugreift, da auch Abhängigkeiten zu früheren Ereignissen besteht.

Das was mich so verwirrt ist ja das nicht die Art der Quelldaten ein Problem ist, sondern die Frequenz!
BlackJack

@chwin: Variablen auf Modulebene sind die die direkt im Modul definiert sind. Und davon sollte man eigentlich *keine* haben.
chwin
User
Beiträge: 16
Registriert: Freitag 4. Juli 2014, 15:07

Hallo
kbr hat geschrieben:Ich hatte mal ein ähnliches Problem und ZMQ stellte sich als der Übeltäter heraus.
Danke für den Tipp, ich glaube das ist genau mein Problem.
AMQPStorn zieht solange vorhanden, unendlich viele Nachrichten in den lokalen Speicher. Diese verschwinden aber nie!
Dieses Problem tritt nur mit no_ack=True auf. Wenn ich no_ack=False setzte und braf meine Nachrichten bestätige bleibt der RAM verbrauch bei wenigen MB genau wie erwartet. Leider bricht dann die Verarbeitungsgeschwindigkeit auf ein fast unbrauchbares Niveau ein.

Ich habe mich jetzt mal an den author von AMQPStorn gewendet. mal gucken ob und was der sagt.



Danke für die Hilfe


gruss
Chris
chwin
User
Beiträge: 16
Registriert: Freitag 4. Juli 2014, 15:07

Es gab binnen Sekunden eine Antwort:
This is indeed a side effect of the current implementation when using no-ack set to True. I'll look into implementing some sort of back pressure functionality that could be used to prevent excessive message buildup.
Der Fehler ist also gefunden

Dank an alle
Antworten