Retour au blog
Analyse Technique · Performances & Benchmarks · 10 min de lecture

Pourquoi l’absence de garbage collector change la latence p99

Affane Daylami · Fondateur· 24 août 2026

p99 mesure la requête la plus lente sur cent — celle qui casse votre SLA pendant que la moyenne reste parfaite. Sur un backend à fort trafic, cette poignée de requêtes lentes a souvent une seule cause : le garbage collector, qui interrompt tout le programme pour libérer de la mémoire. Rust n’a pas de garbage collector. Voici le mécanisme, avec des sources tierces datées plutôt qu’un chiffre marketing.

Cet article s’appuie uniquement sur des sources tierces et datées — jamais sur un chiffre Aurabase inventé. Aucune latence p99 propre à notre backend n’y figure. Nous écrivons notre cœur applicatif en Rust, sans garbage collector : un fait vérifiable directement dans le code, workspace Cargo et services axum. Nous n’avons en revanche pas encore publié de méthodologie de benchmark p99 reproductible pour le démontrer en chiffres. Ce texte explique un mécanisme, pas un résultat mesuré.

L’essentiel
  • p99 mesure la requête la plus lente sur cent — l’endroit où une pause de garbage collector (GC) fait le plus mal, pas sur la moyenne (Dean & Barroso, « The Tail at Scale », Google, 2013).
  • Un GC interrompt tout le programme (« stop-the-world ») pour libérer la mémoire inutilisée. Rust n’a pas de GC : la mémoire se libère au moment précis où une valeur sort de portée, vérifié par le compilateur.
  • Discord a documenté en 2020 un service de cache où Go déclenchait un cycle de garbage collection au moins toutes les deux minutes, chaque cycle provoquant un pic de latence (Discord Engineering Blog).
  • Réduire une pause GC prend des années d’ingénierie, même chez Google : le collecteur de Go est passé de 300-400 ms à 500 µs entre 2015 et 2018, sans jamais atteindre zéro (go.dev).
  • Le cœur backend d’Aurabase est écrit en Rust, sans garbage collector — vérifié dans le code. Aucun chiffre de latence p99 Aurabase n’est publié à ce jour : ceci reste un mécanisme, pas une mesure.
#
Latence de queue

Pourquoi le p99 n’est pas la moyenne

Une moyenne cache l’essentiel. Si 99 requêtes sur 100 répondent en 5 ms et qu’une seule prend 500 ms, la moyenne reste basse. Mais un utilisateur sur cent subit une attente cent fois plus longue. Le p99 mesure exactement cette requête-là : la centième la plus lente, celle qui viole votre SLA pendant que votre tableau de bord de latence moyenne reste vert.

Chez Google, Jeffrey Dean et Luiz André Barroso ont formalisé ce problème dans « The Tail at Scale » (Communications of the ACM, vol. 56, 2013). Leur constat, souvent cité depuis : « Temporary high latency episodes which are unimportant in moderate size systems may come to dominate overall service performance at large scale ». En clair : des épisodes de latence ponctuels, négligeables à petite échelle, finissent par dominer la performance perçue d’un système distribué.

Un backend qui traite des milliers de requêtes par seconde envoie forcément, à un moment ou un autre, une requête qui tombe pendant une pause GC. À grande échelle, ce n’est pas un cas rare. C’est une certitude statistique.

#
Mécanique GC

Ce qu’un garbage collector fait, et pourquoi ça met tout en pause

Un garbage collector (GC) suit en continu les objets vivants d’un programme — ceux encore référencés quelque part — et libère la mémoire des objets devenus inaccessibles. Ce suivi s’appelle le tracing : le GC parcourt le graphe des références, marque ce qui est encore utilisé, puis balaie le reste.

Le problème : parcourir ce graphe pendant que le programme continue à créer de nouvelles références produit des résultats incohérents. La réponse historique, encore utilisée en dernier recours par de nombreux GC modernes, est le stop-the-world — le programme entier se met en pause le temps du marquage et du balayage. Plus le tas mémoire est grand, plus la pause tend à être longue : sa durée dépend de la taille des données vivantes, pas de la charge de travail du moment.

La plupart des GC modernes utilisent une stratégie générationnelle : ils partent du principe que la majorité des objets meurent jeunes. Les allocations récentes sont donc scannées souvent, mais rapidement, dans une petite zone mémoire. Les objets qui survivent plusieurs cycles migrent vers une zone plus grande, scannée rarement — mais quand cette zone doit être nettoyée, la pause associée grandit avec sa taille. C’est cette pause « majeure », pas les petites pauses « mineures », qui domine le p99 d’un service à fort trafic et forte allocation.

Info
Les GC modernes, concurrents et générationnels, réduisent la fréquence et la durée de ces pauses en travaillant en parallèle du programme. Mais ils gardent presque tous un mécanisme de repli stop-the-world pour les cas limites — et le réduire prend des années d’ingénierie. La section 04 en donne un exemple chiffré et sourcé.
#
Cas réel

Discord, 2020 : une pause GC devient un incident de production

En février 2020, l’ingénieur Jesse Howarth publie un billet devenu une référence dans l’industrie : « Why Discord is switching from Go to Rust » (Discord Engineering Blog). Le service concerné, Read States, gère l’état de lecture des messages pour des millions d’utilisateurs — des dizaines de millions d’entrées par cache — avec des centaines de milliers de mises à jour par seconde.

Le diagnostic est direct, cité tel quel dans l’article : « Go will force a garbage collection run every 2 minutes at minimum ». Autrement dit, Go déclenche un cycle de ramasse-miettes au moins toutes les deux minutes sur ce service — et chaque cycle produit un pic de latence visible dans les graphiques de l’équipe.

L’équipe a d’abord réduit la taille du cache pour atténuer les pics. Le compromis restait défavorable : moins de pauses GC, mais plus de requêtes en échec de cache retombant sur la base de données — donc un p99 global dégradé ailleurs. Le correctif de fond a été la réécriture du service en Rust, sans garbage collector à surveiller.

Le billet a suscité un débat technique nourri : plus de 1580 points et 642 commentaires sur Hacker News le jour même de sa publication (4 février 2020) — signe que le problème dépasse largement le cas Discord.

#
Historique Go

Trois ans d’ingénierie chez Google pour faire chuter une pause de 400 ms à 500 µs

Le garbage collector de Go illustre l’ampleur de l’effort nécessaire pour dompter une pause GC — même avec les moyens d’une équipe dédiée chez Google. Rick Hudson, responsable technique du GC de Go, a documenté cette histoire dans deux billets officiels du blog Go.

Avant août 2015300-400 msCollecteur historique de Go, avant refonte
Août 2015 · Go 1.530-40 msPremier collecteur concurrent, objectif < 10 ms fixé
2016 · Go 1.6< 10 ms (SLO tenu)Objectif initial atteint en production
Mars 2017 · Go 1.8sous la millisecondeSuppression du balayage de pile stop-the-world
Août 2017 · Go 1.9100-200 µs (repère)Nouveau repère informel évoqué par l’équipe
2018 · SLO annoncé500 µs par cycleObjectif de service formalisé par Rick Hudson

Source : « Getting to Go: The Journey of Go’s Garbage Collector », go.dev, 12 juillet 2018 ; et « Go GC: Prioritizing low latency and simplicity », go.dev, 31 août 2015.

Trois ans de travail dédié ont fait chuter la pause type d’un facteur mille. Mais la pause n’a jamais disparu : c’est un objectif de service (SLO), pas une garantie absolue à zéro. Un tracing GC doit, par construction, parcourir un graphe d’objets vivants de temps en temps. La seule variable ajustable, c’est la fréquence et la durée de ce parcours — pas son existence.

Ce choix de priorité n’est pas neutre. Go cible en priorité les services réseau et les backends web, où une pause de plusieurs centaines de millisecondes casse directement l’expérience utilisateur — d’où l’effort massif investi sur la latence plutôt que sur le débit brut du GC. D’autres runtimes managés ont hérité d’arbitrages différents, façonnés par leurs cas d’usage historiques, avant de rattraper ce terrain avec leurs propres collecteurs à faible pause. Le point commun reste le même : tous partent d’un tracing GC, donc d’un mécanisme de pause à minimiser — jamais à supprimer par construction.

#
Mécanique Rust

Pourquoi Rust n’a pas ce problème par construction

Rust ne réduit pas les pauses GC : il élimine le mécanisme qui les cause. Le compilateur suit, dès la compilation, qui possède chaque valeur mémoire — c’est l’ownership. Quand le propriétaire d’une valeur sort de portée, Rust insère automatiquement l’appel qui libère cette mémoire, au même endroit dans le code binaire. Ce mécanisme s’appelle RAII (Resource Acquisition Is Initialization) : la libération est déterministe, pas planifiée par un ramasse-miettes qui tourne en arrière-plan.

Alexandru Nedelcu, auteur d’un blog technique reconnu dans l’écosystème Scala/Rust, résume le compromis dans un article récent : « The trade-off that Rust makes is one of ease-of-use, in preference for performance with predictable latency and safety » (alexn.org, 21 juillet 2026). Rust échange une partie de la simplicité d’écriture contre une latence prévisible.

Le même article résume pourquoi les GC modernes ne suffisent pas toujours : « Modern GCs try to do their work incrementally and concurrently, without affecting the program. But their ability is limited, falling back to a stop-the-world GC cycle that freezes the whole program, thus affecting latency ».

Voici le mécanisme en une dizaine de lignes — un exemple générique, pas un extrait du code Aurabase :

example.rs
RUST
struct Connection {
id: u32,
}
impl Drop for Connection {
fn drop(&mut self) {
println!("connexion {} fermée", self.id);
}
}
fn handle_request() {
let conn = Connection { id: 42 };
// ... traite la requête ...
} // conn sort de portée ici : drop() s’exécute
// à l’instant précis, vérifié par le compilateur —
// pas par un cycle de ramasse-miettes.

Nuance importante : tout n’est pas gratuit. Les types à comptage de références (Rc, Arc) ajoutent un petit coût à chaque clonage et chaque libération. Ce coût reste local et déterministe. Il n’y a jamais de pause qui gèle le programme entier le temps de parcourir un tas mémoire.

Précision utile pour un backend asynchrone : le runtime async de Rust (tokio, utilisé par l’ensemble des services Aurabase) n’a rien à voir avec un garbage collector. Il planifie des tâches coopératives sur un pool de threads, mais ne parcourt jamais un graphe d’objets vivants pour libérer de la mémoire. La confusion est fréquente en venant d’écosystèmes où le runtime asynchrone et le GC sont gérés par la même machine virtuelle.

#
Implication architecture

Ce que ça change pour un backend à fort trafic

Sur un backend qui sert des milliers de requêtes concurrentes, l’absence de GC retire une variable de l’équation p99. Plus besoin de dimensionner un tas mémoire, de régler les générations d’un collecteur, ou de surveiller un cycle qui peut tomber au pire moment. La latence d’une requête individuelle dépend de son propre travail, pas d’un événement global imprévisible ailleurs dans le programme.

Le cœur backend d’Aurabase applique ce principe : tous les services (aura-gateway, aura-auth, aura-db, aura-realtime, aura-storage, aura-functions, aura-ai…) sont écrits en Rust, organisés dans un workspace Cargo unique. C’est vérifiable directement dans le dépôt :

Cargo.toml
TOML
[workspace]
resolver = "2"
members = [
"services/aura-gateway",
"services/aura-auth",
"services/aura-db",
# ... 9 autres services + libs partagées
]
[workspace.dependencies]
axum = { version = "0.8", features = ["ws", "multipart", "macros"] }

Extrait réel de Cargo.toml, workspace edition 2021, resolver v2 — vérifié dans le dépôt Aurabase.

Ce que ce fait ne prouve pas, à ce stade : un chiffre de latence p99 mesuré pour Aurabase. Nous n’avons pas encore publié de méthodologie de benchmark reproductible pour notre propre backend — c’est un chantier en cours, pas un résultat disponible aujourd’hui. L’absence de garbage collector est un mécanisme vérifié dans le code. Ce n’est pas, à elle seule, une preuve de latence p99 mesurée. Gardez cette distinction en tête face à n’importe quel argument marketing sur le sujet, y compris le nôtre — voir notre comparatif technique Aurabase vs Supabase pour le détail de l’architecture.

Mesurer un p99 correctement demande sa propre discipline : conditions de charge représentatives, percentiles calculés sur une fenêtre glissante suffisamment large, et environnement de test proche de la production. Publier un chiffre sans cette méthodologie revient à publier un chiffre marketing. C’est exactement ce que nous refusons de faire dans cet article.

#
Nuance

Ce que l’absence de GC ne résout pas

Le no-GC n’est pas une baguette magique
Retirer le garbage collector élimine une seule source de latence de queue — pas toutes. Un backend Rust peut quand même afficher un p99 dégradé à cause de l’attente réseau, d’un pool de connexions Postgres saturé, d’un verrou contesté en base, d’une requête SQL mal indexée, ou d’un appel à une API tierce lente. Le mécanisme décrit dans cet article retire une cause structurelle. Il n’immunise pas contre les autres.

Chez Aurabase par exemple, chaque service communique avec Postgres via un pool de connexions (sqlx) et avec les autres services via NATS JetStream. Un pool sous-dimensionné, un abonnement NATS lent à consommer, ou une requête SQL sans index adapté produisent chacun leur propre pic de latence — indépendamment de l’absence de garbage collector.

La conclusion pratique : l’absence de GC est une bonne raison architecturale de choisir un backend Rust pour un système sensible au p99. Ce n’est pas, à elle seule, une garantie de latence — ni chez Aurabase, ni ailleurs. La méthode qui compte reste la même : mesurer, publier la méthodologie, puis corriger ce que les mesures révèlent. Si vous migrez depuis un backend avec GC, notre guide de migration Supabase vers Aurabase détaille ce qui change et ce qui reste identique.

#
Questions fréquentes

FAQ : garbage collector et latence p99

L’absence de garbage collector garantit-elle un p99 faible ?+
Non. Elle retire une source structurelle de pauses imprévisibles, mais d’autres facteurs — réseau, pool de connexions, verrous base de données — influencent aussi la latence de queue. Voir la section « Ce que l’absence de GC ne résout pas » ci-dessus.
Pourquoi Discord n’a-t-il pas simplement réglé son GC Go différemment ?+
L’équipe a essayé plusieurs ajustements, dont une réduction de la taille du cache, documentés dans son billet de 2020. Le compromis restait défavorable : moins de pauses GC contre plus de requêtes en échec de cache. La réécriture en Rust a supprimé le compromis plutôt que de le déplacer.
Les GC modernes comme celui de Go ont-ils réglé le problème ?+
Ils l’ont massivement réduit — le GC de Go est passé de 300-400 ms à 500 microsecondes entre 2015 et 2018 (go.dev) — mais pas éliminé. Un tracing GC garde, par construction, un mécanisme de pause pour les cas limites.
Aurabase a-t-il publié un benchmark de latence p99 ?+
Pas encore. Le fait vérifié dans le code est l’absence de garbage collector (Rust, workspace Cargo, services axum). Le chiffre de latence p99 mesuré et sa méthodologie reproductible restent à publier — nous préférons l’absence de chiffre à un chiffre non sourcé.
ARCHITECTURE RUST NATIVE

Un backend qui n’a pas de pause à surveiller.

Cœur applicatif 100% Rust, sans garbage collector à régler. Créez un projet et testez la latence par vous-même.

Créer un projet Lire la documentation technique
Aucune carte bancaire requise · 500 MB gratuits · 50 000 MAU