Klempnerprogrammierer oder die Geschichte eines Lecks und die Schwierigkeiten, damit umzugehen

Es war Dienstag, der 25. Februar. Die schwierige Veröffentlichung der Version am Samstag, den 22. Februar, war bereits in der Vergangenheit. Es schien, als sei das Schlimmste zurückgeblieben, und nichts deutete auf Ärger hin. Es änderte sich jedoch alles auf einmal, als ein Überwachungsfehler zu einem Speicherverlust im Koordinatorprozess des Zugriffskontrolldienstes führte.

Woher? Die letzten größeren Änderungen in der Codebasis des Koordinators waren in der vorherigen Version vor mehr als zwei Monaten vorgenommen worden, und danach passierte dem Speicher nichts Bemerkenswertes. Leider waren die Überwachungspläne unnachgiebig - das Gedächtnis des Koordinators begann offensichtlich irgendwo zu lecken, eine große Pfütze wurde auf der Servicefläche zur Schau gestellt, was bedeutet, dass das Sanitär-Team viel zu tun hatte.



Zuerst machen wir einen kleinen Exkurs. Mit VLSI können Sie unter anderem die Arbeitszeiten verfolgen und den Zugriff anhand von Gesichtsmodellen, Fingerabdrücken oder Zugangskarten überwachen . Gleichzeitig kommuniziert VLSI mit Endpoint Controllern (Schlösser, Drehkreuze, Zugangsterminals usw.). Ein separater Dienst kommuniziert mit Geräten. Es ist passiv und interagiert mit Zugriffssteuerungsgeräten basierend auf ihren eigenen Protokollen, die über HTTP (S) implementiert sind. Es basiert auf dem Standardstapel für Services in unserem Unternehmen: Die PostgreSQL-Datenbank Python 3 wird für die Geschäftslogik verwendet und mit C / C ++ - Methoden von unserer Plattform aus erweitert.

Ein typischer Webdienstknoten besteht aus folgenden Prozessen:

  • Monitor ist der Root-Prozess.
  • Ein Koordinator ist ein untergeordneter Prozess eines Monitors.
  • Arbeits prozess.

Monitor ist der erste Webdienstprozess. Seine Aufgabe ist es, fork () aufzurufen, den Prozess des untergeordneten Koordinators zu starten und seine Arbeit zu überwachen. Der Koordinator ist der Hauptprozess des Webdienstes. Er empfängt Anforderungen von externen Geräten, sendet Antworten und gleicht die Last aus. Der Koordinator sendet die Anforderung zur Ausführung an die Arbeitsprozesse, führt sie aus, sendet die Antwort an den gemeinsam genutzten Speicher und informiert den Koordinator darüber, dass die Aufgabe abgeschlossen ist, und Sie können das Ergebnis abrufen.

Wer ist schuld und was zu tun?


Daher unterscheidet sich der Koordinator des Zugriffskontrolldienstes von den Koordinatoren anderer Dienste in unserem Unternehmen durch das Vorhandensein eines Webservers. Die Koordinatoren anderer Dienste arbeiteten ohne Lecks, daher musste das Problem in unserer Konfiguration gesucht werden. Schlimmer noch, auf den Prüfständen gab es die neue Version schon lange, und niemand bemerkte Speicherprobleme. Sie begannen genauer hinzuschauen und stellten fest, dass die Erinnerung nur auf einem der Stände fließt, und selbst dann mit unterschiedlichem Erfolg - was bedeutet, dass das Problem immer noch nicht so leicht reproduzierbar ist.



Was ist zu tun? Wie finde ich einen Grund? Zunächst haben wir einen Speicherauszug erstellt und ihn zur Analyse an Spezialisten von der Plattform gesendet. Das Ergebnis - es gibt nichts im Dump: keinen Grund, keinen Hinweis, in welche Richtung man weiter schauen soll. Wir haben die Änderungen im Code des Koordinators gegenüber der vorherigen Version überprüft - plötzlich haben wir einige schreckliche Änderungen vorgenommen, aber das nicht sofort verstanden? Aber nein - nur ein paar Kommentare wurden zum Koordinatorcode hinzugefügt, aber ein paar Methoden wurden in neue Dateien verschoben - im Allgemeinen nichts Kriminelles.

Sie begannen, auf unsere Kollegen zu schauen - Entwickler des Kerns unseres Dienstes. Sie bestritten zuversichtlich die Möglichkeit einer Beteiligung an unserem Unglück, boten jedoch an, die Tracemalloc-Überwachung in den Dienst zu implantieren. Kaum gesagt als getan, beim nächsten Hotfix schließen wir den Service ab, testen ihn schnell und geben ihn für den Kampf frei.

Und was sehen wir? Jetzt fließt unser Gedächtnis nicht nur schnell, sondern auch sehr schnell weg - das Wachstum ist exponentiell geworden. Der erste Peak wird bösen Mächten und verwandten Faktoren zugeschrieben, aber der zweite Peak, einige Stunden nach dem ersten, macht deutlich, dass es zu lange dauert, bis der nächste Hotfix mit einem solchen Notfallverhalten des Dienstes veröffentlicht wird. Daher nehmen wir die Ergebnisse von tracemalloc und patchen den Service, wobei wir die Änderungen mit Überwachung zurücksetzen, um zumindest zum linearen Wachstum zurückzukehren.



Es schien, als hätten wir die Ergebnisse der Arbeit von tracemalloc für den in Python zugewiesenen Speicher erhalten. Jetzt werden wir sie untersuchen und den Schuldigen des Lecks finden, aber es war nicht vorhanden - in den gesammelten Daten sind keine 5,5-GB-Peaks in den Überwachungsdiagrammen zu sehen. Der maximal verwendete Speicher beträgt nur 250 MB, und sogar traceMalloc belegt 130 MB davon. Dies ist teilweise erklärbar - mit tracemalloc können Sie die Speicherdynamik in Python anzeigen, es ist jedoch nichts über die Speicherzuweisung in C- und C ++ - Paketen bekannt, die von unserer Plattform implementiert werden. Es war nicht möglich, etwas Interessantes in den erhaltenen Daten zu finden, der Speicher wird in akzeptablen Volumina gewöhnlichen Objekten wie Streams, Strings und Wörterbüchern zugewiesen - im Allgemeinen nichts Verdächtiges. Dann haben wir beschlossen, alles Überflüssige aus den Daten zu entfernen, nur den gesamten Speicherverbrauch und die gesamte Zeit zu belassen und zu visualisieren.Obwohl die Visualisierung nicht dazu beigetragen hat, die Fragen „Was passiert“ und „Warum“ zu beantworten, haben wir mit ihrer Hilfe eine Korrelation mit den Daten aus der Überwachung festgestellt - was bedeutet, dass wir definitiv irgendwo ein Problem haben und danach suchen müssen.



Zu diesem Zeitpunkt gingen unserem Klempnerteam die Ideen aus, wo nach einem Leck gesucht werden sollte. Glücklicherweise hat uns der Vogel vorgesungen, dass eine wesentliche Änderung von der Plattform stattgefunden hat - die Python-Version wurde von 3.4 auf 3.7 geändert, und dies ist ein riesiges Suchfeld.

Wir haben uns entschlossen, im Internet nach Problemen im Zusammenhang mit Speicherlecks in Python 3.7 zu suchen, da dieses Verhalten mit Sicherheit bereits auf jemanden gestoßen ist. Trotzdem wurde Python 3.7 vor langer Zeit veröffentlicht, wir haben erst mit dem aktuellen Update darauf umgestellt. Glücklicherweise wurde die Antwort auf unsere Frage schnell gefunden, und es gab auch ein Problem und eine Pull-Anfrage , um das Problem zu beheben, und sie selbst war an den Änderungen beteiligt, die von den Python-Entwicklern vorgenommen wurden.
Was ist passiert?

Ab Version 3.7 hat sich das Verhalten der ThreadingMixIn-Klasse geändert, von der wir von unserem Webserver erben, um jede Anforderung in einem separaten Thread zu verarbeiten. In der ThreadingMixIn-Klasse haben sie den Eintrag aller erstellten Threads zu einem Array hinzugefügt. Aufgrund solcher Änderungen werden Klasseninstanzen, die Geräteverbindungen verarbeiten, nach Abschluss nicht freigegeben, und der Garbage Collector in Python kann den Speicher nicht aus verbrauchten Threads löschen. Dies führte zu einem linearen Wachstum des zugewiesenen Speichers in direktem Verhältnis zur Anzahl der Anforderungen an unseren Server.

Hier ist es, der heimtückische Code des Python-Moduls mit einem großen Loch (der Code in Python 3.5 wird links vor Änderungen angezeigt, rechts - in 3.7 danach):



Als wir den Grund herausfanden, konnten wir das Leck leicht beseitigen: In unserer Erbenklasse haben wir den Wert der Flagge geändert, die das alte Verhalten zurückgegeben hat, und das ist alles - ein Sieg! Streams werden wie zuvor erstellt, ohne in die Klassenvariable zu schreiben, aber wir sehen ein angenehmes Bild in den Überwachungsgraphen - das Leck wurde behoben!



Es ist schön, nach einem Sieg darüber zu schreiben. Wir sind wahrscheinlich nicht die Ersten, die nach dem Wechsel zu Python 3.7 auf dieses Problem stoßen, aber höchstwahrscheinlich nicht die Letzten. Für uns selbst kamen wir zu dem Schluss, dass wir:

  • Gehen Sie ernsthafter vor, um die möglichen Folgen größerer Änderungen zu bewerten, insbesondere wenn andere angewandte Entscheidungen von uns abhängen.
  • Überprüfen Sie Ihren Code bei globalen Änderungen der Plattform, z. B. beim Ändern der Python-Version, auf mögliche Probleme.
  • Reagieren Sie auf verdächtige Änderungen in den Überwachungsplänen nicht nur von Kampfdiensten, sondern auch von Testdiensten. Trotz des aktuellen Garbage Collector gibt es auch in Python Speicherlecks.
  • Bei Speicheranalysetools wie tracemalloc ist Vorsicht geboten, da eine falsche Verwendung die Situation verschlimmern kann.
  • Sie müssen darauf vorbereitet sein, dass die Erkennung von Speicherlecks Geduld, Ausdauer und ein wenig Detektivarbeit erfordert.

Nun, ich möchte allen meinen Dank aussprechen, die bei der Bewältigung der Sanitärnotarbeiten mitgewirkt haben und wieder zu ihrer früheren Arbeitsfähigkeit in unseren Dienst zurückkehren!

All Articles