Dieser Artikel stützt sich ausschließlich auf veraltete Quellen Dritter – niemals auf eine erfundene Aurabase-Chiffre. Es ist keine für unser Backend spezifische p99-Latenz enthalten. Wir schreiben unseren Anwendungskern in Rust, ohne Garbage Collector: eine Tatsache, die direkt im Code, Workspace Cargo und Axum Services überprüft werden kann. Allerdings haben wir noch keine reproduzierbare p99-Benchmark-Methodik veröffentlicht, um dies in Zahlen zu belegen. Dieser Text erklärt einen Mechanismus, kein gemessenes Ergebnis.
Das Wesentliche
- p99 misst die langsamste Abfrage von hundert – die Stelle, an der eine Garbage Collector (GC)-Pause am meisten schmerzt, nicht im Durchschnitt (Dean & Barroso, „The Tail at Scale“, Google, 2013).
- Ein GC unterbricht das gesamte Programm („Stop-the-World“), um ungenutzten Speicher freizugeben. Rust hat keinen GC: Speicher wird genau dann freigegeben, wenn ein Wert den Gültigkeitsbereich verlässt, was vom Compiler überprüft wird.
- Discord dokumentierte im Jahr 2020 einen Cache-Dienst, bei dem Go mindestens alle zwei Minuten einen Garbage-Collection-Zyklus auslöste, wobei jeder Zyklus einen Anstieg der Latenz verursachte (Discord Engineering Blog).
- Die Reduzierung einer GC-Pause erfordert jahrelange Entwicklungsarbeit, selbst bei Google: Der GB-Kollektor stieg zwischen 2015 und 2018 von 300–400 ms auf 500 µs, ohne jemals Null zu erreichen (go.dev).
- Der Backend-Kern von Aurabase ist in Rust geschrieben, ohne Garbage Collector – im Code verifiziert. Bisher wurden keine p99-Aurabase-Latenzzahlen veröffentlicht: Dies bleibt ein Mechanismus, keine Messung.
Warum p99 nicht durchschnittlich ist
Ein Durchschnitt verbirgt das Wesentliche. Wenn 99 von 100 Anfragen in 5 ms antworten und nur eine 500 ms benötigt, bleibt der Durchschnitt niedrig. Aber jeder hundertste Benutzer muss hundertmal länger warten. Der p99 misst genau diese Anfrage: das langsamste Hundertstel, das gegen Ihr SLA verstößt, während Ihr Dashboard für die durchschnittliche Latenz grün bleibt.
Bei Google haben Jeffrey Dean und Luiz André Barroso dieses Problem in „The Tail at Scale“ (Communications of the ACM, Bd. 56, 2013) formalisiert. Ihre Beobachtung wird seitdem oft zitiert: „Vorübergehende Episoden mit hoher Latenz, die in mittelgroßen Systemen unwichtig sind, können in großem Maßstab die Gesamtleistung des Dienstes dominieren.“ Kurz gesagt: Gelegentliche Latenzepisoden, die im kleinen Maßstab vernachlässigbar sind, dominieren letztendlich die wahrgenommene Leistung eines verteilten Systems.
Ein Backend, das Tausende von Anfragen pro Sekunde verarbeitet, muss irgendwann einmal eine Anfrage senden, die während einer GC-Pause fällt. Im großen Maßstab ist dies kein ungewöhnlicher Fall. Dies ist eine statistische Gewissheit.
Was ein Garbage Collector tut und warum er alles anhält
Ein Garbage Collector (GC) verfolgt kontinuierlich die lebenden Objekte eines Programms – diejenigen, auf die noch irgendwo verwiesen wird – und gibt den Speicher von Objekten frei, auf die nicht mehr zugegriffen werden kann. Diese Nachverfolgung wird als tracingbezeichnet: Der GC geht das Referenzdiagramm durch, markiert, was noch verwendet wird, und durchsucht dann den Rest.
Das Problem: Das Durchlaufen dieses Diagramms, während das Programm weiterhin neue Referenzen erstellt, führt zu inkonsistenten Ergebnissen. Die historische Antwort, die von vielen modernen GCs immer noch als letzter Ausweg verwendet wird, ist stop-the-world – das gesamte Programm pausiert während des Markierens und Scannens. Je größer der Heap, desto länger ist in der Regel die Pause: Ihre Dauer hängt von der Größe der Live-Daten ab, nicht von der aktuellen Arbeitslast.
Die meisten modernen GCs verwenden eine Generationenstrategie: Sie gehen davon aus, dass die meisten Objekte jung sterben. Aktuelle Belegungen werden daher häufig, aber schnell, in einem kleinen Speicherbereich gescannt. Objekte, die mehrere Zyklen überleben, wandern in einen größeren Bereich und werden seltener gescannt. Wenn dieser Bereich jedoch gereinigt werden muss, wächst die damit verbundene Pause mit seiner Größe. Es ist diese „große“ Unterbrechung, nicht die kleinen „kleinen“ Unterbrechungen, die das p99 eines stark frequentierten und zuteilungsstarken Dienstes dominiert.
Moderne gleichzeitige und generationsübergreifende GCs reduzieren die Häufigkeit und Dauer dieser Pausen, indem sie parallel zum Programm arbeiten. Aber fast alle von ihnen verfügen über einen „Stop-the-World“-Fallback-Mechanismus für Grenzfälle – und dessen Reduzierung erfordert jahrelange Entwicklungsarbeit. Abschnitt 04 enthält ein quantifiziertes und beschafftes Beispiel.
Discord, 2020: Eine GC-Pause wird zum Produktionsvorfall
Im Februar 2020 veröffentlichte der Ingenieur Jesse Howarth einen Beitrag, der in der Branche zu einer Referenz geworden ist: „Warum Discord von Go zu Rust wechselt“ (Discord Engineering Blog). Der entsprechende Dienst, Read States, verwaltet den Lesestatus von Nachrichten für Millionen von Benutzern – zig Millionen Einträge pro Cache – mit Hunderttausenden Aktualisierungen pro Sekunde.
Die Diagnose ist direkt und wird im Artikel zitiert: „Go erzwingt mindestens alle 2 Minuten einen Garbage Collection-Lauf.“ Mit anderen Worten: Go löst bei diesem Dienst mindestens alle zwei Minuten einen Garbage-Collection-Zyklus aus – und jeder Zyklus führt zu einem Anstieg der Latenz, der in den Diagrammen des Teams sichtbar ist.
Das Team reduzierte zunächst die Cache-Größe, um die Spitzen auszugleichen. Der Kompromiss blieb ungünstig: weniger GC-Pausen, aber mehr Cache-Miss-Anfragen, die auf die Datenbank fallen – daher insgesamt eine Verschlechterung des p99 an anderer Stelle. Die grundlegende Lösung bestand darin, den Dienst in Rust neu zu schreiben, ohne dass ein Garbage Collector überwacht werden musste.
Der Beitrag löste eine lebhafte technische Debatte aus: Mehr als 1.580 Punkte und 642 Kommentare auf Hacker News am selben Tag seiner Veröffentlichung (4. Februar 2020) – ein Zeichen dafür, dass das Problem weit über den Discord-Fall hinausgeht.
Drei Jahre Entwicklungsarbeit bei Google, um eine Pause von 400 ms auf 500 µs zu senken
Der Garbage Collector von Go veranschaulicht den Umfang des Aufwands, der erforderlich ist, um eine GC-Pause zu bändigen – selbst mit den Ressourcen eines engagierten Teams bei Google. Rick Hudson, technischer Leiter von Go GC, hat diese Geschichte in zwei offiziellen Go-Blogbeiträgen dokumentiert.
| Vor August 2015 | 300–400 ms | Historischer Go-Sammler, vor der Neugestaltung |
|---|---|---|
| August 2015 · Go 1.5 | 30-40 ms | Erster konkurrierender Kollektor, Ziel < 10 ms festgelegt |
| 2016 · Go 1.6 | < 10 ms (SLO gehalten) | Erstes Ziel in der Produktion erreicht |
| März 2017 · Go 1.8 | unter der Millisekunde | Stop-the-World-Stack-Scan wird entfernt |
| August 2017 · Go 1.9 | 100-200 µs (Markierung) | Neuer informeller Benchmark, der vom Team erwähnt wurde |
| 2018 · SLO angekündigt | 500 µs pro Zyklus | Von Rick Hudson formalisiertes Serviceziel |
Quelle: „Getting to Go: The Journey of Go’s Garbage Collector“, go.dev, 12. Juli 2018; und „Go GC: Priorisierung geringer Latenz und Einfachheit“, go.dev, 31. August 2015.
Drei Jahre engagierter Arbeit haben die typische Pause um den Faktor Tausend verkürzt. Aber die Pause verschwand nie: Es handelt sich um ein Serviceziel (SLO), nicht um eine absolute Nullgarantie. Eine GC-Ablaufverfolgung muss konstruktionsbedingt von Zeit zu Zeit einen Graphen lebender Objekte durchlaufen. Die einzige einstellbare Variable ist die Häufigkeit und Dauer dieser Reise – nicht ihre Existenz.
Diese Prioritätswahl ist nicht neutral. Go zielt in erster Linie auf Netzwerkdienste und Web-Backends ab, bei denen eine Pause von mehreren hundert Millisekunden das Benutzererlebnis direkt beeinträchtigt – daher der enorme Aufwand, der in die Latenz und nicht in den reinen GC-Durchsatz investiert wird. Andere verwaltete Laufzeiten übernahmen unterschiedliche Kompromisse, die von ihren historischen Anwendungsfällen geprägt waren, bevor sie mit ihren eigenen Low-Pause-Collectors Boden gut machten. Der gemeinsame Punkt bleibt derselbe: Sie alle gehen von der GC-Verfolgung aus, also von einem Pausenmechanismus, der minimiert und niemals durch die Konstruktion beseitigt werden muss.
Warum Rust dieses Problem konstruktionsbedingt nicht hat
Rust reduziert GC-Pausen nicht: Es eliminiert den Mechanismus, der sie verursacht. Der Compiler verfolgt beim Kompilieren, wem der jeweilige Speicherwert gehört – das istownership. Wenn der Besitzer eines Werts den Gültigkeitsbereich verlässt, fügt Rust automatisch den Aufruf, der diesen Speicher freigibt, an derselben Stelle im Binärcode ein. Dieser Mechanismus wird als RAII (Resource Acquisition Is Initialization) bezeichnet: Die Freigabe erfolgt deterministisch und wird nicht durch einen im Hintergrund laufenden Garbage Collector geplant.
Alexandru Nedelcu, Autor eines im Scala/Rust-Ökosystem anerkannten technischen Blogs, fasst den Kompromiss in einem kürzlich erschienenen Artikel zusammen: „Der Kompromiss, den Rust eingeht, besteht in der Benutzerfreundlichkeit, gegenüber Leistung mit vorhersehbarer Latenz und Sicherheit“ (alexn.org, 21. Juli 2026). Rust tauscht einen Teil der Einfachheit des Schreibens gegen vorhersehbare Latenz ein.
Derselbe Artikel fasst zusammen, warum moderne GCs nicht immer ausreichen: „Moderne GCs versuchen, ihre Arbeit inkrementell und gleichzeitig zu erledigen, ohne das Programm zu beeinträchtigen. Aber ihre Fähigkeit ist begrenzt und greift auf einen Stop-the-World-GC-Zyklus zurück, der das gesamte Programm einfriert und so die Latenz beeinträchtigt.“
Hier ist der Mechanismus in etwa zehn Zeilen – ein allgemeines Beispiel, kein Auszug aus dem Aurabase-Code:
Wichtige Nuance: Nicht alles ist kostenlos. Referenzzähltypen (Rc, Arc) verursachen bei jedem Klon und jeder Veröffentlichung geringe Kosten. Diese Kosten bleiben lokal und deterministisch. Es gibt nie eine Pause, die das gesamte Programm einfriert, während es einen Speicherheap durchläuft.
Nützliche Klarstellung für ein asynchrones Backend: Die asynchrone Rust-Laufzeit (tokio, die von allen Aurabase-Diensten verwendet wird) hat nichts mit einem Garbage Collector zu tun. Es plant kooperative Aufgaben für einen Thread-Pool, durchläuft jedoch niemals einen Live-Objektgraphen, um Speicher freizugeben. In Ökosystemen, in denen die asynchrone Laufzeit und der GC von derselben virtuellen Maschine verwaltet werden, kommt es häufig zu Verwirrung.
Was sich dadurch für ein Backend mit hohem Datenverkehr ändert
In einem Backend, das Tausende gleichzeitiger Anfragen bearbeitet, wird durch das Fehlen von GC eine Variable aus der Gleichung p99 entfernt. Es ist nicht mehr erforderlich, die Größe eines Speicher-Heaps zu bestimmen, die Generationen eines Kollektors anzupassen oder einen Zyklus zu überwachen, der zum ungünstigsten Zeitpunkt ausfallen kann. Die Latenz einer einzelnen Anfrage hängt von ihrer eigenen Arbeit ab und nicht von einem unvorhersehbaren globalen Ereignis an anderer Stelle im Programm.
Der Backend-Kern von Aurabase wendet dieses Prinzip an: Alle Dienste (aura-gateway, aura-auth, aura-db, aura-realtime, aura-storage, aura-functions, aura-ai…) sind in Rust geschrieben und in einem einzigen Cargo-Arbeitsbereichorganisiert. Dies kann direkt im Repository überprüft werden:
Tatsächlicher Auszug aus Cargo.toml, Workspace Edition 2021, Resolver v2 – im Aurabase-Repository überprüft.
Was diese Tatsache zum jetzigen Zeitpunkt noch nicht beweist: ein für Aurabase gemessener p99-Latenzwert. Wir haben noch keine reproduzierbare Benchmark-Methodik für unser eigenes Backend veröffentlicht – dies ist eine laufende Arbeit und kein heute verfügbares Ergebnis. Das Fehlen eines Garbage Collectors ist ein im Code verifizierter Mechanismus. Dies allein ist kein Beweis für die gemessene p99-Latenz. Behalten Sie diese Unterscheidung bei allen Marketingargumenten zu diesem Thema, einschließlich unserer, im Hinterkopf – Einzelheiten zur Architektur finden Sie in unserem technischen Vergleich Aurabase vs. Supabase.
Die korrekte Messung eines p99 erfordert eine eigene Disziplin: repräsentative Lastbedingungen, über ein ausreichend breites Schiebefenster berechnete Perzentile und eine produktionsnahe Testumgebung. Die Veröffentlichung einer Zahl ohne diese Methodik ist wie die Veröffentlichung einer Marketingzahl. Genau das verweigern wir in diesem Artikel.
Was das Fehlen von GC nicht löst
Durch das Entfernen des Garbage Collectors wird nur eine Quelle der Tail-Latenz beseitigt – nicht alle. Ein Rust-Backend kann aufgrund von Netzwerkwartezeiten, einem gesättigten Postgres-Verbindungspool, einer umstrittenen Datenbanksperre, einer schlecht indizierten SQL-Abfrage oder einem langsamen API-Aufruf eines Drittanbieters immer noch einen verschlechterten p99 anzeigen. Der in diesem Artikel beschriebene Mechanismus beseitigt eine strukturelle Ursache. Es bietet keine Immunität gegen andere.
Bei Aurabase beispielsweise kommuniziert jeder Dienst über einen Verbindungspool (sqlx) mit Postgres und über NATS JetStreammit anderen Diensten. Ein zu kleiner Pool, ein NATS-Abonnement mit langsamer Nutzung oder eine SQL-Abfrage ohne geeigneten Index erzeugen jeweils ihre eigene Latenzspitze – unabhängig vom Fehlen eines Garbage Collectors.
Die praktische Schlussfolgerung: Das Fehlen von GC ist ein guter architektonischer Grund, ein Rust-Backend für ein p99-fähiges System zu wählen. Dies ist an sich keine Garantie für Latenz – weder bei Aurabase noch anderswo. Die Methode, auf die es ankommt, bleibt dieselbe: Messen, die Methodik veröffentlichen und dann korrigieren, was die Messungen ergeben. Wenn Sie von einem Backend mit GC migrieren, erfahren Sie in unserem Migrationsleitfaden Supabase zu Aurabase, was sich ändert und was gleich bleibt.