Dit artikel is uitsluitend gebaseerd op gedateerde bronnen van derden – nooit op een verzonnen Aurabase-cijfer. Er is geen p99-latentie inbegrepen die specifiek is voor onze backend. We schrijven onze applicatiekern in Rust, zonder garbage collector: een feit dat direct kan worden geverifieerd in de code, werkruimte Cargo en axum services. We hebben echter nog geen reproduceerbare p99-benchmarkmethodologie gepubliceerd om dit in cijfers aan te tonen. Deze tekst legt een mechanisme uit, niet een gemeten resultaat.
De essentie
- p99 meet de langzaamste zoekopdracht van de honderd – de plaats waar een pauze van een garbage collector (GC) het meeste pijn doet, niet gemiddeld (Dean & Barroso, “The Tail at Scale”, Google, 2013).
- Een GC onderbreekt het hele programma (“stop-the-world”) om ongebruikt geheugen vrij te maken. Rust heeft geen GC: geheugen wordt vrijgegeven op het precieze moment waarop een waarde buiten bereik valt, geverifieerd door de compiler.
- Discord documenteerde in 2020 een cacheservice waarbij Go minstens elke twee minuten een garbage collection-cyclus activeerde, waarbij elke cyclus een piek in de latentie veroorzaakte (Discord Engineering Blog).
- Het verkleinen van een GC-pauze vergt jaren van engineering, zelfs bij Google: de GB-collector ging van 300-400 ms naar 500 µs tussen 2015 en 2018, zonder ooit nul te bereiken (go.dev).
- De backend-kern van Aurabase is geschreven in Rust, zonder garbage collector – geverifieerd in de code. Tot nu toe zijn er geen p99-latentiecijfers van Aurabase gepubliceerd: dit blijft een mechanisme, geen meting.
Waarom p99 niet gemiddeld is
Een gemiddelde verbergt het essentiële. Als 99 van de 100 verzoeken binnen 5 ms reageren en slechts één verzoek 500 ms duurt, blijft het gemiddelde laag. Maar één op de honderd gebruikers moet honderd keer langer wachten. De p99 meet precies dit verzoek: de langzaamste honderdste, degene die uw SLA schendt terwijl uw gemiddelde latentiedashboard groen blijft.
Bij Google hebben Jeffrey Dean en Luiz André Barroso dit probleem geformaliseerd in “The Tail at Scale” (Communicatie van de ACM, vol. 56, 2013). Hun observatie, sindsdien vaak geciteerd: “Tijdelijke episoden met hoge latentie, die onbelangrijk zijn in systemen van gemiddelde omvang, kunnen de algehele serviceprestaties op grote schaal gaan domineren”. Kortom: incidentele perioden van latentie, die op kleine schaal te verwaarlozen zijn, domineren uiteindelijk de waargenomen prestaties van een gedistribueerd systeem.
Een backend die duizenden verzoeken per seconde verwerkt, zal op een gegeven moment onvermijdelijk een verzoek verzenden dat tijdens een GC-pauze mislukt. Op grote schaal is dit geen ongewoon geval. Dit is een statistische zekerheid.
Wat een vuilnisman doet en waarom hij alles pauzeert
Een garbage collector (GC) volgt voortdurend de levende objecten van een programma – waar nog ergens naar wordt verwezen – en maakt het geheugen vrij van objecten die ontoegankelijk zijn geworden. Deze tracking wordt tracinggenoemd: de GC doorloopt de referentiegrafiek, markeert wat nog wordt gebruikt en veegt vervolgens de rest door.
Het probleem: het doorlopen van deze grafiek terwijl het programma doorgaat met het creëren van nieuwe referenties levert inconsistente resultaten op. Het historische antwoord, dat door veel moderne GC's nog steeds als laatste redmiddel wordt gebruikt, is stop-the-world: het hele programma pauzeert tijdens het markeren en scannen. Hoe groter de hoop, hoe langer de pauze doorgaans is: de duur ervan hangt af van de grootte van de live gegevens, niet van de huidige werklast.
De meeste moderne GC’s gebruiken een generatiestrategie: ze gaan ervan uit dat de meerderheid van de objecten jong sterft. Recente toewijzingen worden daarom vaak, maar snel, gescand in een klein geheugengebied. Objecten die verschillende cycli overleven, migreren naar een groter gebied en worden niet vaak gescand – maar wanneer dat gebied moet worden schoongemaakt, groeit de bijbehorende pauze met de omvang ervan. Het is deze “grote” pauze, en niet de kleine “kleine” pauzes, die de p99 domineert van een dienst met veel verkeer en hoge toewijzingen.
Moderne gelijktijdige en generatie-GC's verminderen de frequentie en duur van deze pauzes door parallel aan het programma te werken. Maar ze hanteren bijna allemaal een stop-the-world fallback-mechanisme voor grensgevallen – en het terugdringen ervan vergt jaren van engineering. Paragraaf 04 geeft een gekwantificeerd en gedocumenteerd voorbeeld.
Discord, 2020: een pauze in het klassement wordt een productie-incident
In februari 2020 publiceerde ingenieur Jesse Howarth een bericht dat een referentie is geworden in de branche: “Waarom Discord overschakelt van Go naar Rust” (Discord Engineering Blog). De relevante dienst, Read States, beheert de leesstatus van berichten voor miljoenen gebruikers (tientallen miljoenen vermeldingen per cache) met honderdduizenden updates per seconde.
De diagnose is direct en wordt geciteerd zoals in het artikel: “Go zal minimaal elke 2 minuten een afvalinzamelingsactie forceren”. Met andere woorden: Go activeert minstens elke twee minuten een garbagecollection-cyclus op deze service – en elke cyclus produceert een piek in de latentie die zichtbaar is in de grafieken van het team.
Het team heeft eerst de cachegrootte verkleind om de pieken glad te strijken. Het compromis bleef ongunstig: minder GC-pauzes, maar meer cache-miss-verzoeken die in de database terechtkwamen – en dus een verslechterde algehele p99 elders. De basisoplossing was het herschrijven van de service in Rust, zonder dat er een garbage collector in de gaten moest worden gehouden.
Het bericht leidde tot een levendig technisch debat: meer dan 1.580 punten en 642 reacties op Hacker News op dezelfde dag van publicatie (4 februari 2020) – een teken dat het probleem veel verder gaat dan de Discord-zaak.
Drie jaar engineering bij Google om een pauze te verlagen van 400 ms naar 500 µs
De Go-garbagecollector illustreert de omvang van de inspanning die nodig is om een GC-pauze te temmen – zelfs met de middelen van een toegewijd team bij Google. Rick Hudson, technisch hoofd van Go GC, documenteerde dit verhaal in twee officiële Go-blogposts.
| Vóór augustus 2015 | 300-400 ms | Historische Go-verzamelaar, vóór herontwerp |
|---|---|---|
| Augustus 2015 · Ga 1.5 | 30-40 ms | Eerste concurrerende collector, doel < 10 ms ingesteld |
| 2016 · Ga 1.6 | < 10 ms (SLO vastgehouden) | Initiële doelstelling behaald in productie |
| Maart 2017 · Ga 1.8 | onder de milliseconde | Stop-the-world stapelscan verwijderen |
| Augustus 2017 · Ga 1.9 | 100-200 µs (markering) | Nieuwe informele benchmark genoemd door het team |
| 2018 · SLO aangekondigd | 500 µs per cyclus | Servicedoelstelling geformaliseerd door Rick Hudson |
Bron: “Getting to Go: The Journey of Go’s Garbage Collector”, go.dev, 12 juli 2018; en “Go GC: prioriteit geven aan lage latentie en eenvoud”, go.dev, 31 augustus 2015.
Drie jaar toegewijd werk heeft de typische pauze met een factor duizend verminderd. Maar de pauze is nooit verdwenen: het is een servicedoelstelling (SLO), geen absolute garantie voor nul. Een GC-tracering moet, door de constructie, van tijd tot tijd een grafiek van levende objecten doorkruisen. De enige instelbare variabele is de frequentie en duur van deze reis – niet het bestaan ervan.
Deze prioriteitskeuze is niet neutraal. Go richt zich primair op netwerkdiensten en web-backends, waarbij een pauze van enkele honderden milliseconden de gebruikerservaring direct onderbreekt. Vandaar de enorme inspanning die is geïnvesteerd in latentie in plaats van in ruwe GC-doorvoer. Andere beheerde runtimes erfden verschillende afwegingen, gevormd door hun historische gebruiksscenario's, voordat ze terrein innamen met hun eigen verzamelaars met lage pauze. Het gemeenschappelijke punt blijft hetzelfde: ze beginnen allemaal met GC-tracering, en dus met een pauzemechanisme dat geminimaliseerd moet worden – dat nooit door de constructie geëlimineerd mag worden.
Waarom Rust dit probleem niet heeft door de constructie
Roest vermindert de GC-pauzes niet: het elimineert het mechanisme dat deze veroorzaakt. De compiler houdt bij het compileren bij wie de eigenaar is van elke geheugenwaarde: dit isownership. Wanneer de eigenaar van een waarde buiten bereik valt, voegt Rust automatisch de oproep in die dat geheugen vrijmaakt, op dezelfde plaats in de binaire code. Dit mechanisme heet RAII (Resource Acquisition Is Initialization): de release is deterministisch en niet gepland door een garbage collector die op de achtergrond draait.
Alexandru Nedelcu, auteur van een technische blog erkend in het Scala/Rust-ecosysteem, vat de afweging samen in een recent artikel: “De afweging die Rust maakt is er een van gebruiksgemak, in voorkeur voor prestaties met voorspelbare latentie en veiligheid” (alexn.org, 21 juli 2026). Rust ruilt een deel van de eenvoud van schrijven in voor voorspelbare latentie.
Hetzelfde artikel vat samen waarom moderne GC's niet altijd voldoende zijn: "Moderne GC's proberen hun werk stapsgewijs en gelijktijdig uit te voeren, zonder het programma te beïnvloeden. Maar hun mogelijkheden zijn beperkt en vallen terug op een stop-the-world GC-cyclus die het hele programma bevriest en zo de latentie beïnvloedt".
Hier is het mechanisme in ongeveer tien regels – een algemeen voorbeeld, geen uittreksel uit de Aurabase-code:
Belangrijke nuance: niet alles is gratis. Typen voor het tellen van referenties (Rc, Arc) brengen kleine kosten met zich mee voor elke kloon en release. Deze kosten blijven lokaal en deterministisch. Er is nooit een pauze die het hele programma bevriest terwijl het door een geheugenheap gaat.
Nuttige verduidelijking voor een asynchrone backend: de Rust asynchrone runtime (tokio, gebruikt door alle Aurabase-services) heeft niets te maken met een garbage collector. Het plant samenwerkingstaken op een pool van threads, maar doorloopt nooit een live objectgrafiek om geheugen vrij te maken. Verwarring is gebruikelijk als gevolg van ecosystemen waar de asynchrone runtime en de GC worden beheerd door dezelfde virtuele machine.
Wat dit verandert voor een backend met veel verkeer
Op een backend die duizenden gelijktijdige verzoeken verwerkt, verwijdert de afwezigheid van GC een variabele uit de vergelijking p99. Het is niet meer nodig om een geheugenhoop te verkleinen, de generaties van een verzamelaar aan te passen of een cyclus te monitoren die op het slechtste moment kan mislukken. De latentie van een individueel verzoek hangt af van zijn eigen werk, en niet van een onvoorspelbare mondiale gebeurtenis elders in het programma.
De backend-kern van Aurabase past dit principe toe: alle services (aura-gateway, aura-auth, aura-db, aura-realtime, aura-storage, aura-functions, aura-ai…) zijn geschreven in Rust, georganiseerd in één Cargo-werkruimte. Dit kan rechtstreeks in de repository worden geverifieerd:
Werkelijk uittreksel uit Cargo.toml, werkruimteeditie 2021, solver v2 — geverifieerd in de Aurabase-repository.
Wat dit feit in dit stadium niet bewijst: een latentiecijfer van p99 gemeten voor Aurabase. We hebben nog geen reproduceerbare benchmarkmethodologie voor onze eigen backend gepubliceerd – dit is werk in uitvoering en geen resultaat dat vandaag beschikbaar is. De afwezigheid van een garbage collector is een mechanisme dat in de code is geverifieerd. Dit alleen is geen bewijs van gemeten p99-latentie. Houd dit onderscheid in gedachten bij elk marketingargument over dit onderwerp, inclusief het onze. Zie onze technische vergelijking Aurabase versus Supabase voor details van de architectuur.
Het correct meten van een p99 vereist een eigen discipline: representatieve belastingsomstandigheden, percentielen berekend over een voldoende breed schuifvenster en een testomgeving dicht bij de productie. Het publiceren van een cijfer zonder deze methodologie is als het publiceren van een marketingcijfer. Dit is precies wat we in dit artikel weigeren te doen.
Wat de afwezigheid van GC niet oplost
Het verwijderen van de garbage collector elimineert slechts één bron van staartlatentie – niet alle. Een Rust-backend kan nog steeds een verslechterde p99 vertonen als gevolg van wachten op het netwerk, een verzadigde Postgres-verbindingspool, een betwiste databasevergrendeling, een slecht geïndexeerde SQL-query of een trage API-aanroep van derden. Het in dit artikel beschreven mechanisme neemt een structurele oorzaak weg. Het biedt geen immuniteit tegen anderen.
Bij Aurabase communiceert elke service bijvoorbeeld met Postgres via een verbindingspool (sqlx) en met andere services via NATS JetStream. Een te kleine pool, een traag te consumeren NATS-abonnement of een SQL-query zonder een geschikte index veroorzaken elk hun eigen latentiepiek, ongeacht de afwezigheid van een garbage collector.
De praktische conclusie: de afwezigheid van GC is een goede architectonische reden om voor een Rust-backend te kiezen voor een p99-bewust systeem. Dit is op zichzelf geen garantie voor latentie – noch bij Aurabase, noch elders. De methode die er toe doet blijft hetzelfde: meet, publiceer de methodologie en corrigeer vervolgens wat de metingen onthullen. Als u migreert vanuit een backend met GC, beschrijft onze Supabase naar Aurabase migratiegids wat er verandert en wat hetzelfde blijft.