Chaos Performance¶
Le Chaos Performance regroupe huit leviers d'infrastructure backend
exposés par ChaosService et exécutés par quatre composants techniques :
ChaosInterceptor (thread pool, slow query, réseau), CpuChaosScheduler
(CPU), MemoryLeakSimulator (fuite mémoire), GcPressureSimulator
(pression GC) et DbPoolChaosScheduler (saturation HikariCP). Le
OrderService porte en plus l'injection deadlock spécifique au checkout.
Chaque levier est un slider indépendant 0 – 100 (sauf la mémoire qui
utilise deux sliders couplés) piloté par POST /api/admin/chaos/<levier>.
Vue d'ensemble des leviers¶
| Levier | Plage | Classe technique | Endpoint admin |
|---|---|---|---|
| CPU | 0 – 100 | CpuChaosScheduler |
POST /api/admin/chaos/cpu |
| CPU ratio | 1 – 5 | CpuChaosScheduler |
POST /api/admin/chaos/cpu |
| Fuite mémoire | 0 – 105 | MemoryLeakSimulator |
POST /api/admin/chaos/memory |
| Garde-fou mémoire | 0 – 100 | MemoryLeakSimulator |
POST /api/admin/chaos/memory |
| Pression GC | 0 – 100 | GcPressureSimulator |
POST /api/admin/chaos/gc-pressure |
| Pool DB | 0 – 100 | DbPoolChaosScheduler |
POST /api/admin/chaos/db-pool |
| Pool threads | 0 – 100 | ChaosInterceptor |
POST /api/admin/chaos/thread-pool |
| Slow query | 0 – 100 | ChaosInterceptor |
POST /api/admin/chaos/slow-queries |
| Deadlock | 0 – 100 | OrderService |
POST /api/admin/chaos/deadlock |
| Timeout réseau | 0 – 100 | ChaosInterceptor |
POST /api/admin/chaos/network |
Tous les endpoints exigent le header X-Admin-Token ou une session admin
valide. La valeur retournée suit le schéma { success, message, status }
où status est l'état complet de ChaosService.getStatus().
CPU — CpuChaosScheduler¶
Classe : CpuChaosScheduler.java
Métriques : chaos_intensity{type="cpu"}, container_cpu_usage
Le scheduler s'exécute en @Scheduled(fixedRate = 100) et soumet à un pool
de threads dédié (Executors.newFixedThreadPool(5)) une boucle de hashage
SHA-256 calibrée pour saturer le CPU de manière contrôlée. La formule :
Calibrage de référence : sur un Intel i7-8700T, intensity = 100 avec
ratio = 1 produit ≈ 100 % de charge sur un cœur. Le paramètre ratio
multiplie le nombre de threads parallèles soumis au pool — il sert à
adapter le chaos aux machines plus puissantes (Ryzen 5800X et au-delà).
Activation¶
curl -X POST https://perfshop-api.perfshop.io/api/admin/chaos/cpu \
-H "X-Admin-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"intensity": 80, "ratio": 2}'
Observation¶
- Métrique principale :
container_cpu_usage(Gauge 0.0 – 1.0) - Effet de bord :
http_server_requests_seconds{quantile="0.99"}augmente proportionnellement - Logs :
[BackendChaos] CPU intensity set to: 80à chaque changement
Mémoire — MemoryLeakSimulator¶
Classe : MemoryLeakSimulator.java (version v3-bidirectional)
Métriques : chaos_intensity{type="memory"}, chaos_guardrail{type="memory"},
jvm_memory_used_bytes{area="heap"}
Le simulateur mémoire utilise deux sliders couplés pour permettre une fuite mémoire progressive et bornée :
| Slider | Plage | Sémantique |
|---|---|---|
memoryLeakTarget |
0 – 100 | Pourcentage de la capacité du garde-fou à remplir |
memoryLeakTarget |
105 | Valeur spéciale — OOM intentionnel sans plafond |
memoryGuardrail |
0 – 100 | Plafond de sécurité en % du heap max (-Xmx), défaut 80 |
Formule effective¶
cible_effective_pct = target × guardrail / 100
cible_effective_bytes = -Xmx × cible_effective_pct / 100
Le cap porte sur le heap occupé (totalMemory - freeMemory), pas
seulement sur la mémoire allouée par le simulateur. Tant que le heap
occupé est sous la cible, le simulateur alloue 5 % de -Xmx par seconde.
Dès qu'il dépasse la cible, il libère 2 blocs par tick et déclenche
System.gc().
Mode OOM intentionnel¶
La combinaison target = 105 + guardrail = 100 désactive le garde-fou
et alloue jusqu'à provoquer un OutOfMemoryError. C'est la seule
configuration qui peut faire crasher la JVM — les autres combinaisons sont
bornées par le garde-fou.
Exemples sur -Xmx 1g¶
target |
guardrail |
Heap cible | Comportement |
|---|---|---|---|
| 0 | 80 | 0 MB | Désactivé — libère toute la mémoire |
| 50 | 80 | 410 MB | Plateau à ~40 % du heap |
| 100 | 80 | 820 MB | Plateau à 80 % — garde-fou actif |
| 100 | 50 | 512 MB | Plateau à 50 % |
| 105 | 80 | 820 MB | Mode OOM bridé par garde-fou à 80 % |
| 105 | 100 | ∞ | OOM intentionnel — crash JVM |
Comportement bidirectionnel¶
Le slider est réactif dans les deux sens. Quand le formateur baisse
memoryLeakTarget de 100 à 50 %, le simulateur libère progressivement les
blocs alloués (2 par seconde) jusqu'à repasser sous la nouvelle cible. À 0,
toute la mémoire fuitée est libérée d'un coup et System.gc() est appelé.
Activation¶
curl -X POST https://perfshop-api.perfshop.io/api/admin/chaos/memory \
-H "X-Admin-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"intensity": 100, "guardrail": 80}'
Le champ intensity correspond à memoryLeakTarget, guardrail à
memoryGuardrail. Le second est optionnel — s'il est omis, la valeur
courante est conservée (défaut 80).
Pression GC — GcPressureSimulator¶
Classe : GcPressureSimulator.java (version v2-sawtooth-cycle)
Métrique : chaos_intensity{type="gc_pressure"}
Contrairement à MemoryLeakSimulator qui produit un plateau (la mémoire
fuitée n'est pas libérée tant que le slider reste haut), le simulateur de
pression GC alloue puis libère cycliquement, produisant un pattern en
dents de scie caractéristique sur le graphique heap de Grafana.
Cycle FILL → RELEASE → PAUSE¶
gantt
title Cycle GC pressure (intensity = 50%, -Xmx 1g)
dateFormat s
axisFormat %Ss
section Phase
FILL +75 MB :a1, 0, 1s
FILL +75 MB :a2, 1, 1s
FILL +75 MB :a3, 2, 1s
FILL +75 MB :a4, 3, 1s
RELEASE all + GC :crit, a5, 4, 1s
PAUSE :a6, 5, 1s
PAUSE :a7, 6, 1s
| Phase | Durée | Action |
|---|---|---|
| FILL | 4 ticks | Allocation d'un bloc par tick (20 % × intensity / 100) |
| RELEASE | 1 tick | clear() du buffer + System.gc() |
| PAUSE | 2 ticks | Aucune action — laisser le GC finir |
Le cycle complet dure 7 secondes quel que soit le -Xmx. À
intensity = 50 sur -Xmx 1g, chaque cycle alloue ≈ 300 MB en 4 secondes
puis libère brutalement. À intensity = 100, les dents montent jusqu'à
saturer le heap — le GC devient extrêmement agressif.
Activation¶
curl -X POST https://perfshop-api.perfshop.io/api/admin/chaos/gc-pressure \
-H "X-Admin-Token: $TOKEN" \
-d '{"intensity": 50}'
Observation¶
- Métrique principale :
jvm_memory_used_bytes{area="heap"}— pattern dents de scie - Métriques GC :
jvm_gc_pause_seconds_countaugmente,jvm_gc_pause_seconds_sumégalement ;jvm_gc_pause_seconds_maxpeut atteindre plusieurs centaines de ms - Effet de bord : pics CPU parasites visibles dans
container_cpu_usage - Logs :
[BackendChaos] GcPressure: FILL +75 MB — heap=...à chaque tick
Pool DB — DbPoolChaosScheduler¶
Classe : DbPoolChaosScheduler.java
Métriques : chaos_intensity{type="db_pool"}, hikaricp_connections_active,
hikaricp_connections_pending
Le scheduler vole un nombre proportionnel de connexions HikariCP en les
maintenant ouvertes avec setAutoCommit(false) (transaction non commitée).
La taille maximale du pool est lue depuis
spring.datasource.hikari.maximum-pool-size (défaut 20).
Formule¶
Au moins 1 connexion reste libre pour ne pas deadlocker le scheduler
lui-même. À intensity = 100 avec un pool de 20, le scheduler bloque 19
connexions — toutes les nouvelles requêtes attendent jusqu'au timeout
HikariCP (par défaut 30 s).
Pool de threads Tomcat — ChaosInterceptor¶
Classe : ChaosInterceptor.applyThreadPoolChaos()
Métriques : chaos_intensity{type="thread_pool"}, tomcat_threads_busy_threads
Pour chaque requête HTTP non exclue, l'interceptor applique un
Thread.sleep proportionnel à l'intensité — calibré pour saturer
progressivement le pool Tomcat (200 threads par défaut). La formule :
| Intensité | Délai appliqué |
|---|---|
| 1 – 24 % | intensity × 20 ms |
| 25 – 49 % | intensity × 30 ms |
| 50 – 74 % | 1 500 ms fixe |
| 75 – 99 % | 3 000 ms fixe |
| 100 % | 5 000 ms fixe |
À 25 % le délai est de 750 ms, à 50 % il bondit à 1 500 ms : la formule
est volontairement progressive aux paliers bas et palier-fixe au-delà,
pour produire une dégradation graduelle visible dans Grafana plutôt qu'un
saut brutal. La forme exacte est documentée dans le code source de
ChaosInterceptor.
Slow Query — ChaosInterceptor¶
Classe : ChaosInterceptor.applySlowQueryChaos()
Métriques : chaos_intensity{type="slow_query"},
http_server_requests_seconds{quantile="0.99"}
Identique au pool de threads dans le principe (formule similaire) mais
appliqué uniquement aux endpoints hors /api/products — ce dernier est
géré par ProductService.applySlowQueryChaos() pour éviter le cumul.
| Intensité | Délai appliqué |
|---|---|
| 1 – 24 % | intensity × 15 ms |
| 25 – 49 % | intensity × 25 ms |
| 50 – 74 % | 2 000 ms fixe |
| 75 – 99 % | 4 000 ms fixe |
| 100 % | 6 000 ms fixe |
Deadlock — OrderService¶
Métrique : chaos_intensity{type="deadlock"}
Endpoint : POST /api/admin/chaos/deadlock
L'injection deadlock est portée par OrderService au moment du checkout.
Elle simule deux transactions concurrentes acquérant des verrous dans
l'ordre inverse (anti-pattern classique : SELECT … FOR UPDATE sur deux
produits dans des ordres opposés). À haute intensité, la transaction est
annulée par MySQL avec l'erreur Deadlock found when trying to get lock
et le client reçoit un HTTP 500 avec le message i18n
order.error.deadlock.
Comme les deadlocks sont par nature probabilistes, le niveau slider règle la probabilité de déclenchement par checkout, pas une durée fixe.
Timeout réseau — ChaosInterceptor¶
Classe : ChaosInterceptor.applyNetworkChaos()
Métriques : chaos_intensity{type="network"}, comptage HTTP 503
Le Network Chaos couvre tous les endpoints du parcours utilisateur :
/api/orders, /api/auth, /api/products, /api/cart, /api/checkout.
La probabilité de déclenchement est proportionnelle à l'intensité (à
intensity = 30, 30 % des requêtes ciblées sont impactées).
| Intensité | Délai appliqué |
|---|---|
| 1 – 49 % | intensity × 20 ms |
| 50 – 74 % | 1 500 ms fixe |
| 75 – 99 % | 3 000 ms fixe |
| 100 % | 6 000 ms fixe |
À partir de 75 %, l'interceptor a en plus 20 % de chance d'envoyer un
HTTP 503 avec un header Retry-After: 5, simulant un service amont
indisponible. Le délai max est plafonné à 6 secondes pour rester compatible
avec les délais ajoutés par OrderService.processPaymentPublic() (+ 4 s
maximum), sous le timeout client usuel de 15 s.
Reset global¶
POST /api/admin/chaos/reset remet toutes les intensités Performance à
zéro (mémoire, GC, DB pool, threads, CPU, slow query, deadlock, réseau),
libère immédiatement la mémoire fuitée et le buffer GC, et réinitialise en
prime le Chaos Frontend, le Chaos Scripting, Métier, Fonctionnel et
Sécurité. C'est l'équivalent du bouton « Reset all chaos » du
formateur.
Les valeurs par défaut après reset :
| Levier | Valeur post-reset |
|---|---|
cpuRatio |
1 |
memoryGuardrail |
80 % |
| Tous les autres | 0 |
Pertinence pédagogique¶
Chaque levier est calqué sur une cause réelle d'incident en production :
| Levier | Cause réelle illustrée |
|---|---|
| CPU | Calcul lourd synchrone non offload (hashage, cryptographie) |
| Mémoire | Cache sans éviction, écouteurs JPA non détachés, statics qui grossissent |
| GC pressure | Allocations excessives par requête, pas de pooling d'objets |
| Pool DB | FetchType.EAGER sur grandes collections, transactions trop longues |
| Pool threads | Appels synchrones vers un service externe lent, sans timeout |
| Slow query | Index manquants découverts sous charge |
| Deadlock | Transactions acquérant des verrous dans des ordres opposés |
| Network | Service de paiement aval dégradé sans circuit breaker |
Endpoint de statut¶
Retourne l'état complet (backend + frontend + business + functional) sans authentification. C'est l'endpoint de référence du monitoring temps réel.