k Si tu veux devenir vraiment pro en Go backend, comprendre le GC et l’escape analysis
te met au-dessus de 80% des devs.
On va structurer ça clairement :
🧠 1️⃣ Garbage Collector en Go — Ce que tu
dois vraiment savoir
Go utilise un GC concurrent, tri-color, mark-and-sweep, optimisé pour faible latence.
🔹 1.1 Les bases indispensables
Tu dois comprendre :
Heap vs Stack
Ce qui déclenche une allocation heap
Comment le GC trace les objets vivants
Notion de roots
o Variables globales
o Stack
o Registres
Un pro doit savoir répondre à :
Pourquoi mon service consomme 3GB de RAM ?
🔹 1.2 Fonctionnement simplifié du GC
Le GC Go fonctionne en 3 phases :
1. Mark (marquage des objets vivants)
2. Sweep (libération mémoire)
3. Reclaim
Il est :
Concurrent (tourne pendant ton programme)
Low-latency
Non compactant (important)
🔹 1.3 Ce que tu dois maîtriser concrètement
✅ Lire les métriques mémoire
import "runtime"
var m [Link]
[Link](&m)
Tu dois comprendre :
HeapAlloc
HeapSys
NumGC
PauseTotalNs
✅ Profiling mémoire avec pprof
Indispensable en prod.
go tool pprof
Tu dois savoir :
Identifier allocations chaudes
Lire un flamegraph
Voir qui alloue le plus
✅ GOGC
Variable d’environnement :
GOGC=100
Tu dois comprendre :
Plus GOGC est élevé → moins de GC → plus de RAM
Plus GOGC est faible → plus de GC → moins de RAM
En backend haute charge, c’est critique.
🧠 2️⃣ Escape Analysis — Là où les vrais
pros se démarquent
L’escape analysis décide :
Cette variable va sur la stack ou sur le heap ?
Stack = rapide
Heap = lent + GC impact
🔹 2.1 Voir l’escape analysis
Compile avec :
go build -gcflags="-m"
Tu verras :
moved to heap
escapes to heap
Un pro lit ça régulièrement.
🔹 2.2 Quand ça escape ?
Typiquement :
❌ Retourner un pointeur local
func foo() *int {
x := 10
return &x // escape
}
❌ Interface usage
func print(i interface{}) {
[Link](i)
}
Souvent → allocation heap
❌ Closures capturant variables
func main() {
x := 10
go func() {
[Link](x) // escape
}()
}
🔹 2.3 Ce que tu dois savoir optimiser
Éviter allocations inutiles
Préférer struct value quand possible
Éviter conversion excessive vers interface{}
Réutiliser buffers
Utiliser [Link] intelligemment
🔥 3️⃣ Niveau Senior — Ce qui impressionne
en entretien
Tu dois comprendre :
🟣 Pourquoi trop d’allocations tuent la perf
Chaque allocation heap :
Ajoute pression GC
Augmente latence
Dégrade throughput
En API à 10k req/s → ça explose vite.
🟣 Write Barrier
Le GC Go utilise une write barrier pour garder la cohérence pendant le mark phase.
Tu n’as pas besoin de l’implémenter,
mais tu dois comprendre que :
Modifier des pointeurs pendant le GC a un coût.
🟣 STW (Stop The World)
Go essaie de minimiser les pauses STW,
mais elles existent.
Tu dois savoir :
Les mesurer
Les analyser
🎯 Ce que TU dois maîtriser concrètement
Checklist pro :
Savoir lire pprof mémoire
Comprendre Heap vs Stack
Savoir pourquoi une variable escape
Optimiser allocations dans hot path
Comprendre GOGC
Lire [Link]
Savoir quand utiliser [Link]
Comprendre impact GC sur latence
🚀 Mini exercice pour devenir solide
1. Crée une API simple
2. Bombarde-la avec wrk
3. Active pprof
4. Analyse :
o allocations
o GC frequency
o pause times
5. Optimise
6. Mesure avant / après
Ça, c’est niveau senior.
Si tu veux, je peux te faire :
🧪 Un labo pratique GC étape par étape
🧠 Une explication ultra deep du tri-color algorithm
🏆 Comment optimiser une API à 50k req/s
📊 Un exemple réel de flamegraph analysé
Tu veux qu’on aille jusqu’où ?
les 4
Parfait 😎
Là on rentre dans le territoire senior / performance engineer Go.
Je vais te donner les 4 :
1️⃣ Labo pratique GC
2️⃣ Tri-color algorithm deep dive
3️⃣ Optimiser une API à 50k req/s
4️⃣ Analyse d’un flamegraph réel
🧪 1️⃣ LABO PRATIQUE — Comprendre le
GC en conditions réelles
🎯 Objectif
Voir l’impact des allocations sur :
Latence
Throughput
Fréquence GC
Pauses STW
Étape 1 — API volontairement mauvaise
package main
import (
"fmt"
"net/http"
)
func handler(w [Link], r *[Link]) {
data := []byte([Link]("Hello %s", [Link]))
[Link](data)
}
func main() {
[Link]("/", handler)
[Link](":8080", nil)
}
Ici :
[Link] → allocation
conversion string → []byte → allocation
Étape 2 — Load test
Utilise :
wrk
wrk -t4 -c100 -d30s [Link]
Étape 3 — Activer pprof
Ajoute :
import _ "net/http/pprof"
Puis :
go tool pprof [Link]
Tu dois observer :
allocations dominées par fmt
augmentation NumGC
HeapAlloc qui monte vite
Étape 4 — Optimisation
Version optimisée :
func handler(w [Link], r *[Link]) {
[Link]([]byte("Hello"))
}
Encore mieux :
func handler(w [Link], r *[Link]) {
[Link]([]byte("Hello"))
}
Ou réutiliser un buffer global.
Compare :
alloc/op
latency
GC cycles
🔥 Là tu vois l’impact réel.
🧠 2️⃣ TRI-COLOR MARKING —
Explication Deep
Le GC Go est basé sur l’algorithme tri-color.
3 couleurs :
⚪ Blanc → potentiellement mort
⚫ Noir → vivant confirmé
🔘 Gris → en cours d’analyse
Processus :
1. Tous les objets = blanc
2. Les roots deviennent gris
3. On scanne les gris :
o ils deviennent noirs
o leurs enfants deviennent gris
4. Les blancs restants → supprimés
Pourquoi write barrier ?
Pendant que le GC tourne, ton programme modifie des pointeurs.
La write barrier garantit :
Aucun objet vivant ne reste blanc.
Sans ça → corruption mémoire.
🚀 3️⃣ Optimiser une API à 50k req/s
À ce niveau, les problèmes viennent de :
Allocations excessives
JSON encoding
Logging
Contention mutex
GC pressure
🔥 Stratégies pro
1️⃣ Réduire allocations
Éviter interface{}
Éviter fmt
Préallouer slices
Utiliser [Link]
[Link] pour gros objets
2️⃣ JSON ultra optimisé
Au lieu de encoding/json classique :
utiliser [Link] réutilisé
ou librairie plus rapide
3️⃣ Profiling CPU
go tool pprof [Link]
Cherche :
encoding/json
[Link]
[Link]
Si tu vois [Link] haut → trop d’allocations.
4️⃣ Ajuster GOGC
Par défaut :
GOGC=100
En très haute charge :
GOGC=200
→ Moins de GC
→ Plus de RAM
→ Moins de pauses
📊 4️⃣ Lire un Flamegraph comme un senior
Quand tu génères un flamegraph :
go tool pprof -http=:8081 [Link]
Tu verras :
Large rectangles = fonctions dominantes
Large + haut = problème critique
Cas réel typique
Tu vois :
encoding/[Link]
↓
[Link]
↓
[Link]
Conclusion :
Trop de réflexion
Trop d’allocations
GC pressure
Solution :
Struct simple
Éviter tags complexes
Réutiliser buffers
🏆 Niveau Expert — Ce que peu de devs
savent
🔥 Latence P99 impactée par GC
Même si moyenne OK
GC peut tuer ton P99.
Un senior regarde :
tail latency
pas seulement average
🔥 Trop de goroutines = pression GC
Chaque goroutine a sa stack.
Beaucoup de goroutines → plus de roots → plus de travail GC.
🔥 [Link] mal utilisé peut empirer
Si objets trop petits → inutile
Si charge instable → inefficace
🎯 Résumé — Si tu maîtrises tout ça
Tu peux :
Debugger un service Go qui consomme 10GB RAM
Expliquer pourquoi une API spike en latence
Optimiser un service à 100k req/s
Impressionner en entretien senior/staff
Si tu veux on peut maintenant :
🧬 Simuler une vraie prod avec métriques
🧱 Construire une API ultra optimisée ensemble
🧠 Lire le code du runtime GC de Go
⚔️Faire un challenge optimisation (je te donne un code pourri à optimiser)
On monte jusqu’à quel niveau ?
encore ue fois les 4
Parfait. On passe en mode staff engineer.
Je vais refaire les 4, mais encore plus profonds, plus réalistes, plus “prod”.
🧪 1️⃣ LABO PRO — Simulation d’un
service qui souffre en prod
🎯 Objectif
Diagnostiquer :
RAM qui explose
Latence P99 instable
GC trop fréquent
CPU élevé
🧱 Étape 1 — API volontairement toxique
package main
import (
"encoding/json"
"net/http"
)
type User struct {
ID int
Name string
Data []byte
}
func handler(w [Link], r *[Link]) {
users := make([]User, 0)
for i := 0; i < 1000; i++ {
users = append(users, User{
ID: i,
Name: "user",
Data: make([]byte, 1024),
})
}
[Link](w).Encode(users)
}
func main() {
[Link]("/", handler)
[Link](":8080", nil)
}
🔥 Chaque requête :
1000 structs
1000 slices de 1KB
JSON reflection
Allocations massives
⚡ Étape 2 — Stress test
Utilise :
wrk
wrk -t8 -c500 -d60s [Link]
🔎 Étape 3 — Profiling
Activer :
import _ "net/http/pprof"
Puis :
go tool pprof [Link]
Tu vas voir :
[Link] énorme
encoding/json en haut
GC très fréquent
🧠 Étape 4 — Diagnostic
Problèmes :
1. Trop d’allocations
2. Slice non préallouée correctement
3. Data inutile
4. JSON reflection coûteux
🛠 Étape 5 — Optimisation version senior
Supprimer Data
Préallouer slice : make([]User, 1000)
Réutiliser buffers
Pagination au lieu de 1000 items
Tu mesures :
alloc/op
GC cycles
P99 latency
👉 Là tu passes du “je code” au “je maîtrise le runtime”.
🧠 2️⃣ TRI-COLOR — Niveau runtime
interne
Go utilise un GC concurrent mark & sweep tri-color non compactant.
🟢 Le vrai problème : mutations pendant le marquage
Pendant la phase mark :
Le programme continue
Les pointeurs changent
Sans protection → objets vivants supprimés.
⚔️La Write Barrier
À chaque écriture de pointeur :
[Link] = newValue
Le runtime intercepte et garantit :
Si newValue est blanc → le rendre gris
C’est ce qui maintient l’invariant tri-color :
Aucun objet noir ne pointe vers un blanc.
🛑 STW (Stop The World)
Même si Go est concurrent :
Il y a 2 STW courts :
Début du mark
Fin du mark
En haute charge :
Beaucoup de roots
Beaucoup de goroutines
→ STW plus long
🧬 Ce que peu savent
Plus tu as de goroutines → plus le GC scanne de stacks
Chaque goroutine = root
Les stacks grandissent dynamiquement
Donc :
100k goroutines = coût GC réel.
🚀 3️⃣ Optimiser une API à 50k req/s
(réellement)
À ce niveau, tu dois penser :
Allocation budget par requête
CPU cycles
GC budget
Tail latency
🎯 Budget cible réaliste
Pour 50k req/s :
< 1–2 allocs par requête
< 200 bytes alloc/request
GC stable
🔥 Techniques avancées
1️⃣ Struct packing
Ordre des champs :
type A struct {
ID int64
Flag bool
}
Peut générer padding.
Optimiser l’ordre réduit mémoire.
2️⃣ Éviter interface{}
Chaque conversion peut provoquer escape.
3️⃣ Reuse via [Link]
Bon pour :
Buffers
Gros objets temporaires
Mauvais pour :
Petits objets
Objets courts
4️⃣ Réduction JSON cost
encoding/json est lent car :
reflection
allocations
Solutions :
encoder manuel
reuse buffer
streaming
5️⃣ Ajuster GOGC intelligemment
Exemple :
GOGC=200
Moins de GC
Plus de RAM
Moins de pauses
Mais si RAM limitée → dangereux.
📊 4️⃣ Lecture d’un flamegraph réel
Commande :
go tool pprof -http=:8080 [Link]
🔎 Cas typique 1
[Link]
→ Trop d’allocations
🔎 Cas typique 2
encoding/[Link]
→ Reflection lourde
🔎 Cas typique 3
[Link]
→ Trop d’objets heap
🔎 Cas typique 4
[Link]
→ GC trop fréquent
🎯 Ce qu’un senior fait
Il ne regarde PAS seulement :
alloc_space
Il regarde :
inuse_space
P99 latency
Corrélation GC spike ↔ latency spike
🏆 Niveau Staff — Ce que 99% ne savent pas
🔥 La latence moyenne ne sert à rien
Toujours analyser :
P95
P99
Max
🔥 Un service peut être CPU-bound ET GC-bound
Les deux peuvent se combiner.
🔥 Trop optimiser peut empirer
Exemple :
[Link] mal dimensionné
contention mutex
false sharing
🧠 Si tu maîtrises vraiment ces 4
Tu peux :
Debugger une fuite mémoire
Expliquer un spike P99
Réduire RAM d’un service de 40%
Doubler throughput sans changer hardware
Maintenant dis-moi :
On passe à quoi ?
🧬 Lire le code du GC dans le runtime Go
🧠 Faire un vrai challenge optimisation
⚔️Simuler une fuite mémoire distribuée
🚀 Construire une API ultra low-allocation ensemble
On continue jusqu’au niveau “Google SRE” ?
toujours les 4
Excellent 😈
On refait les 4, mais cette fois au niveau “je peux debugger un service Go en prod à 3h du
matin”.
On va faire :
1️⃣ Simulation d’un incident réel
2️⃣ GC interne encore plus deep
3️⃣ Optimisation hardcore 50k+ req/s
4️⃣ Lecture avancée de flamegraph (corrélation GC ↔ latence)
1️⃣ 🧪 INCIDENT PROD RÉALISTE
🚨 Symptômes
RAM passe de 500MB → 6GB en 20 min
P99 passe de 20ms → 800ms
CPU OK
Pas de panic
Tu es on-call.
🔎 Étape 1 — Vérifier les stats runtime
Ajoute temporairement :
import (
"log"
"runtime"
"time"
)
func monitor() {
for {
var m [Link]
[Link](&m)
[Link]("HeapAlloc=%dMB NumGC=%d PauseTotal=%dms\n",
[Link]/1024/1024,
[Link],
[Link]/1e6)
[Link](5 * [Link])
}
}
Ce que tu observes :
HeapAlloc monte en continu
NumGC augmente
PauseTotal augmente
👉 Donc ce n’est pas une absence de GC
👉 C’est une pression allocation constante
🔬 Étape 2 — Heap profile
go tool pprof [Link]
Tu vois :
encoding/[Link]
[Link]
Conclusion :
Chaque requête alloue massivement.
🎯 Étape 3 — Hypothèse
Quelqu’un a ajouté :
[Link](bigStruct)
bigStruct contient :
Gros slices
Nested maps
Interfaces
🔥 Résultat :
allocations massives
pression GC
latence P99 détruite
2️⃣ 🧠 GC INTERNE — VERSION
RUNTIME ENGINEER
Le GC Go est :
Concurrent
Incremental
Non compactant
Tri-color
Hybrid write barrier
🧬 Ce que peu savent : Pacing algorithm
Le GC n’attend pas que la mémoire explose.
Il calcule :
next_gc = heap_live * (1 + GOGC/100)
Si GOGC=100
→ GC déclenché quand heap double.
🔥 Pourquoi trop d’allocations tue la perf
Chaque allocation :
1. Passe par [Link]
2. Peut déclencher un assist GC
🧨 GC Assist (peu connu)
Quand le programme alloue trop vite :
Le mutator (ton code) est forcé d’aider le GC.
Donc :
Ton handler HTTP fait du travail GC.
Ça détruit la latence.
3️⃣ 🚀 Optimiser réellement 50k req/s
On vise :
< 1 alloc / request
Stable heap
P99 stable
🔥 Technique 1 — Zéro allocation string
❌ Mauvais :
[Link]("user-%d", id)
✅ Bon :
buf := make([]byte, 0, 32)
buf = append(buf, "user-"...)
buf = [Link](buf, int64(id), 10)
🔥 Technique 2 — Éviter map[string]interface{}
Chaque interface = potentiel escape.
🔥 Technique 3 — Préallocation stricte
users := make([]User, 1000) // pas 0 capacity
🔥 Technique 4 — Batch processing
Au lieu de 1000 objets par requête
→ pagination
Moins d’objets heap
Moins de GC roots
Moins de scan
🔥 Technique 5 — Limiter goroutines
100k goroutines =
100k stacks
100k roots
Scan GC massif
Un worker pool est souvent meilleur.
4️⃣ 📊 Flamegraph — Lecture Expert
Commande :
go tool pprof -http=:8080 [Link]
🎯 Cas A — [Link] large
→ Trop d’objets heap
→ Structure trop fragmentée
Solution :
Réduire nested structs
Réduire pointeurs
🎯 Cas B — [Link]
🔥 Très mauvais signe
Signifie :
Ton code aide le GC car il alloue trop vite.
🎯 Cas C — [Link]
GC sweep lourd
→ Heap trop gros
→ GOGC mal réglé
🎯 Corrélation Pro
Tu dois corréler :
GC cycle timestamp
Spike latence
Heap growth
Un senior :
Ne regarde jamais un seul graphique.
Il croise :
Heap
CPU
Goroutines
P99
GC cycles
🏆 Ce que tu dois savoir expliquer en
entretien Staff
1. Pourquoi le GC Go n’est pas compactant
2. Pourquoi trop de goroutines impacte le GC
3. Ce qu’est GC assist
4. Comment réduire alloc/op
5. Pourquoi P99 est plus important que moyenne
6. Comment tuning GOGC affecte latence
🔥 Si tu maîtrises vraiment ces 4
Tu peux :
Diviser RAM par 3
Stabiliser P99 sous charge
Debugger fuite mémoire
Expliquer [Link] dans un flamegraph
Concevoir API ultra low-allocation
Maintenant on peut aller encore plus loin :
🧬 Lire directement le code du GC dans le runtime Go
⚔️Faire un challenge d’optimisation chronométré
🧠 Simuler une vraie fuite mémoire subtile
🚀 Construire un microservice ultra low-latency ensemble
Tu veux encore les 4 ? 😈