Aller au contenu

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 }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 :

iterations = intensity × 3 200
threads parallèles = ratio (1 – 5)

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_count augmente, jvm_gc_pause_seconds_sum également ; jvm_gc_pause_seconds_max peut 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

target_blocked = floor(intensity / 100 × (hikari_max_pool − 1))

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

curl -X POST https://perfshop-api.perfshop.io/api/admin/chaos/reset \
  -H "X-Admin-Token: $TOKEN"

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

curl https://perfshop-api.perfshop.io/api/chaos/public/status

Retourne l'état complet (backend + frontend + business + functional) sans authentification. C'est l'endpoint de référence du monitoring temps réel.