Während eines bestimmten kontinuierlichen Stresstests stellten wir fest, dass der Frontend-Knotenspeicher von GreptimeDB auch bei stabilem Anforderungsvolumen weiter zunahm, bis er durch OOM beendet wurde. Wir kamen zu dem Schluss, dass im Frontend ein Speicherleck vorliegen sollte, und begannen daher mit der Behebung von Speicherlecks.
Heap-Profiling
Bei großen Projekten ist es fast unmöglich, Speicherlecks allein durch einen Blick auf den Code zu finden. Daher müssen wir zunächst eine statistische Analyse der Speichernutzung des Programms durchführen. Glücklicherweise verfügt der von GreptimeDB verwendete Jemalloc über Heap-Profiling , und wir unterstützen auch den Export der Jemalloc-Profil-Dump-Datei . Als der Speicher des Frontend-Knotens von GreptimeDB 300 MB bzw. 800 MB erreichte, haben wir die entsprechenden Speicherprofildateien gelöscht, dann das integrierte Tool von jemalloc verwendet, jeprof
um die Speicherunterschiede ( --base
Parameter) zwischen den beiden zu analysieren, und sie schließlich mit einem Flammendiagramm angezeigt:
Offensichtlich wird der lange Block in der Bildmitte von den immer größer werdenden 500 MB Speicher belegt. Bei sorgfältiger Beobachtung gibt es Thread-bezogene Stapelspuren. Könnte es sein, dass zu viele Threads erstellt wurden? Ich habe ps -T -p
den Befehl einfach mehrmals verwendet, um den Prozess des Frontend-Knotens zu überprüfen. Die Anzahl der Threads lag stabil bei 84, und es waren alles Threads, deren Erstellung vorhergesagt wurde. Damit kann der Grund „zu viele Threads“ beseitigt werden.
Als wir weiter nach unten schauten, fanden wir viele Stack-Traces im Zusammenhang mit der Tokio-Laufzeit, und Tokio-Task-Lecks sind ebenfalls ein häufiges Speicherleck. Zu diesem Zeitpunkt werden wir ein anderes Artefakt verwenden: Tokio-console .
Tokio-Konsole
Tokio Console ist das offizielle Diagnosetool von Tokio. Die Ausgabeergebnisse sind wie folgt:
Wir sehen, dass tatsächlich 5559 Aufgaben ausgeführt werden und sich die meisten davon im Leerlaufzustand befinden! Wir können also bestätigen, dass der Speicherverlust in Tokios Aufgabe auftritt. Nun stellt sich die Frage: Wo im GreptimeDB-Code werden so viele Tokio-Aufgaben erzeugt, die nicht beendet werden können?
Aus der Spalte „Standort“ in der obigen Abbildung können wir sehen, wo die Aufgabe erzeugt wird :
impl Runtime {
/// Spawn a future and execute it in this thread pool
///
/// Similar to Tokio::runtime::Runtime::spawn()
pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
self.handle.spawn(future)
}
}
Die nächste Aufgabe besteht darin, den gesamten Code in GreptimeDB zu finden, der diese Methode aufruft.
..Default::default()
!
Nach sorgfältiger Prüfung des Codes haben wir schließlich herausgefunden, wo die Tokio-Aufgabe durchgesickert ist, und das Leck in PR #1512 behoben . Einfach ausgedrückt: Wir haben im Konstruktor einer Struktur, die häufig erstellt wird, eine Tokio-Aufgabe erzeugt, die weiterhin im Hintergrund ausgeführt werden kann, diese jedoch nicht rechtzeitig wiederverwenden konnte. Für die Ressourcenverwaltung stellt das Erstellen der Aufgabe im Konstruktor selbst kein Problem dar, solange Drop
die Aufgabe erfolgreich beendet werden kann. Das Schlimme an unserem Speicherverlust ist, dass wir diese Konvention ignoriert haben.
Dieser Konstruktor Default::default()
wird auch in der Methode der Struktur aufgerufen, was es für uns schwieriger macht, die Grundursache zu finden.
Rust verfügt über eine sehr praktische Methode zum Erstellen einer eigenen Struktur mithilfe einer anderen Struktur, nämlich „ Struct Update Syntax “. Wenn struct implementiert ist Default
, können wir es einfach im Feldkonstruktor von struct verwenden ..Default::default()
. Wenn Default::default()
es einen „Nebeneffekt“ im Inneren gibt (z. B. der Grund für unseren Speicherverlust dieses Mal - das Erstellen einer Tokio-Aufgabe, die im Hintergrund läuft), muss besonders darauf geachtet werden: Nach Abschluss der Strukturkonstruktion wird die Default
temporäre Struktur erstellt werden entsorgt. Machen Sie einen guten Job beim Ressourcenrecycling.
Zum Beispiel das folgende kleine Beispiel: (Rust Playground )
struct A {
i: i32,
}
impl Default for A {
fn default() -> Self {
println!("called A::default()");
A { i: 42 }
}
}
#[derive(Default)]
struct B {
a: A,
i: i32,
}
impl B {
fn new(a: A) -> Self {
B {
a,
// A::default() is called in B::default(), even though "a" is provided here.
..Default::default()
}
}
}
fn main() {
let a = A { i: 1 };
let b = B::new(a);
println!("{}", b.a.i);
}
Die Methode von Struktur A default
wird aufgerufen und ausgedruckt called A::default()
.
Zusammenfassen
- Um Speicherlecks in Rust-Programmen zu beheben, können wir die Heap-Profilierung von jemalloc verwenden, um Dump-Dateien zu exportieren und dann ein Flame-Diagramm zu erstellen, um die Speichernutzung visuell anzuzeigen.
- Die Tokio-Konsole kann den Task-Laufstatus der Tokio-Laufzeit problemlos anzeigen. Achten Sie dabei besonders auf die wachsenden Leerlauf-Tasks.
- Versuchen Sie, keinen Code mit Nebenwirkungen im Konstruktor häufig verwendeter Strukturen zu belassen.
Default
Sollte nur für Werttypstrukturen verwendet werden.
Über Greptime
Greptime Greptime Technology wurde 2022 gegründet und verbessert und entwickelt derzeit zwei Produkte, die Zeitreihendatenbank GreptimeDB und GreptimeCloud.
GreptimeDB ist eine in der Rust-Sprache geschriebene Zeitreihendatenbank. Sie ist verteilt, Open Source, Cloud-nativ und hochkompatibel. Sie hilft Unternehmen, Zeitreihendaten in Echtzeit zu lesen, zu schreiben, zu verarbeiten und zu analysieren und gleichzeitig die Kosten langfristig zu senken Lagerung.
Basierend auf der Open-Source-Lösung GreptimeDB bietet GreptimeCloud Benutzern vollständig verwaltetes DBaaS sowie Anwendungsprodukte in Kombination mit Observability, Internet of Things und anderen Bereichen. Durch die Nutzung der Cloud zur Bereitstellung von Software und Diensten können eine schnelle Self-Service-Bereitstellung und -Bereitstellung, standardisierte Betriebs- und Wartungsunterstützung sowie eine bessere Ressourcenflexibilität erreicht werden. GreptimeCloud wurde offiziell für interne Tests geöffnet. Folgen Sie dem offiziellen Konto oder der offiziellen Website für die neuesten Entwicklungen!
Offizielle Website: https://greptime.com/
Öffentliches Konto: GreptimeDB
GitHub: https://github.com/GreptimeTeam/greptimedb
Dokumentation: https://docs.greptime.com/
Twitter: https://twitter.com/Greptime
Slack: https://greptime.com/slack
LinkedIn: https://www.linkedin.com/company/greptime/
Ein in den 1990er Jahren geborener Programmierer hat eine Videoportierungssoftware entwickelt und in weniger als einem Jahr über 7 Millionen verdient. Das Ende war sehr bestrafend! High-School-Schüler erstellen im Rahmen einer Coming-of-Age-Zeremonie ihre eigene Open-Source-Programmiersprache – scharfe Kommentare von Internetnutzern: Der inländische Dienst Taobao (taobao.com) verließ sich aufgrund des grassierenden Betrugs auf RustDesk und stellte die inländischen Dienste ein und startete die Arbeit zur Optimierung der Webversion von Java neu 17 ist die am häufigsten verwendete Java LTS-Version. Windows 11 erreicht weiterhin einen Rückgang. Open Source Daily unterstützt die Übernahme von Open Source Rabbit R1; Electric schließt die offene Plattform Apple veröffentlicht M4-Chip Google löscht Android Universal Kernel (ACK) Unterstützung für RISC-V-Architektur Yunfeng ist von Alibaba zurückgetreten und plant, in Zukunft unabhängige Spiele für Windows-Plattformen zu produzieren