Chaos Fonctionnel¶
Le Chaos Fonctionnel injecte des exceptions Java réelles dans le parcours e-commerce de PerfShop. Contrairement au Chaos Backend (dégradation infrastructure) ou au Chaos Métier (anomalies logique), ces anomalies cassent des fonctionnalités applicatives avec des exceptions JVM identifiables dans les traces APM.
Principe pédagogique¶
Les étudiants doivent identifier l'exception, localiser la méthode fautive et expliquer la chaîne de causalité — pas corriger le code.
Règle d'or
Les anomalies fonctionnelles se distinguent visuellement dans Tempo par leur status ERROR et leur exception visible dans les attributs de span. C'est la différence avec le chaos backend qui dégrade les métriques sans exception.
Les anomalies sont cumulatives : le niveau 2 inclut les anomalies du niveau 1, etc.
Ne bloque pas le checkout
Même en niveau 3, une commande peut toujours aboutir avec les données de test correctes. Les anomalies F1, F2, F3 sont probabilistes ou conditionnelles — elles ne bloquent pas systématiquement toutes les requêtes.
Niveaux¶
| Niveau | Label | Anomalies actives | Profil étudiant |
|---|---|---|---|
| 0 | Désactivé | 0 | Référence — tout nominal |
| 1 | Junior | 1 (F1) | Identifier une NPE dans APM |
| 2 | Confirmé | 2 (F1 + F2) | Différencier NPE vs SOE |
| 3 | Expert | 3 (F1 + F2 + F3) | Corrélation OOM + heap JVM |
| 4 | Master | 4 (F1 + F2 + F3 + F4) | Détecter une corruption silencieuse HTTP 200 |
Anomalie F1 — NullPointerException dans le paiement¶
Niveau actif : Junior (1), Confirmé (2), Expert (3)
Implémentation : FunctionalChaosService.applyF1PaymentNpe() → appelé dans OrderService.processPaymentPublic()
// Niveau 0 : paiement normal — aucune exception
void processPaymentPublic(String cardNumber, ...) { ... }
// Niveau 1+ : NPE injectée — simulation d'un objet null non géré
public void applyF1PaymentNpe() {
String s = null;
s.length(); // → NullPointerException
}
Impact visible :
- Toute commande déclenche un
HTTP 500avec le messageNullPointerException - Le span
POST /api/ordersapparaît en rouge (ERROR) dans Tempo - L'attribut
exception.type = java.lang.NullPointerExceptionest visible dans les détails du span - L'attribut
exception.stacktracepointe surFunctionalChaosService.applyF1PaymentNpe()
Ce que l'étudiant doit documenter
- Symptôme : 100% des commandes échouent en HTTP 500
- Cause : NullPointerException dans la méthode de paiement
- Méthode fautive :
FunctionalChaosService.applyF1PaymentNpe()appelée depuisprocessPaymentPublic() - Cause racine réelle (en production) : objet non initialisé — ex. résultat de service externe null sans vérification
Métriques impactées :
http_server_requests_seconds_count{status="5xx", uri="/api/orders"}→ 100%jvm_threads_states_threads{state="runnable"}→ stable (pas de thread bloqué)
Anomalie F2 — StackOverflowError sur le calcul de commande¶
Niveau actif : Confirmé (2), Expert (3)
Implémentation : FunctionalChaosService.applyF2OrderStackOverflow() → appelé dans OrderService.createOrderFromItems()
// Récursion infinie déclenchée au calcul du total de la commande
public BigDecimal applyF2OrderStackOverflow() {
return applyF2OrderStackOverflow(); // → StackOverflowError après ~5 000 appels récursifs
}
Impact visible :
- Les commandes échouent avec
HTTP 500et le messageStackOverflowError - Distinct de F1 : le span en erreur est celui du calcul de la commande, pas du paiement
- Dans Tempo :
exception.type = java.lang.StackOverflowError - Le stacktrace est tronqué (trop profond) — comportement caractéristique d'une SOE
- Le span présente une durée anormalement longue avant le crash (parcours de la pile)
Ce que l'étudiant doit documenter
- Symptôme : HTTP 500 au moment du calcul du total (avant le paiement)
- Cause : StackOverflowError — récursion infinie détectée par la JVM
- Distinction F1 vs F2 : la SOE survient avant le paiement (F1 survient pendant le paiement)
- Cause racine réelle (en production) : appel récursif sans condition d'arrêt — ex. calcul de remise ou de frais de port récursif
Métriques impactées :
http_server_requests_seconds_count{status="5xx"}→ montejvm_threads_states_threads{state="runnable"}→ stable
Niveau cumulatif
En niveau 2, F1 et F2 sont actifs simultanément. Les commandes peuvent échouer avec l'une ou l'autre exception selon l'ordre d'appel. L'étudiant doit distinguer les deux dans les traces.
Anomalie F3 — OutOfMemoryError sur le catalogue¶
Niveau actif : Expert (3)
Implémentation : FunctionalChaosService.applyF3CatalogOom() → appelé dans ProductService.getAllProducts()
// Boucle infinie — OOM garanti, ~125 itérations sur -Xmx1g (~2-4s)
// Blocs de 8 Mo : fenêtre suffisante pour Pyroscope (100Hz) d'échantillonner
public void applyF3CatalogOom() {
List<byte[]> leak = new ArrayList<>();
while (true) {
leak.add(new byte[8 * 1024 * 1024]); // 8 Mo par bloc
}
}
Impact visible :
- Les appels au catalogue produits (
GET /api/products) déclenchent une montée rapide du heap JVM - Après plusieurs appels,
java.lang.OutOfMemoryError: Java heap space - Le heap JVM monte de façon non linéaire et non récupérée — distinguable du chaos Memory Leak backend qui monte linéairement et se résorbe quand l'intensité repasse à 0
- Dans Tempo :
exception.type = java.lang.OutOfMemoryErrorsur les spansGET /api/products
Ce que l'étudiant doit documenter
- Symptôme : page catalogue inaccessible (HTTP 500), heap JVM en croissance rapide
- Cause : OutOfMemoryError — allocation massive non libérable (boucle infinie de blocs 8 Mo)
- Corrélation : observer simultanément le flamegraph Pyroscope —
applyF3CatalogOomapparaît en large plateau dans le flamegraph heap (profilmemory:alloc_objects). La boucle dure ~2-4s avant OOM, ce qui laisse le temps au profiler de l'échantillonner. - Cause racine réelle (en production) : ORM chargement
FetchType.EAGERsur collection massive, ou cache non borné
Métriques impactées :
jvm_memory_used_bytes{area="heap"}→ montée rapide et non récupéréejvm_gc_pause_seconds_sum→ GC s'emballe avant le crashhttp_server_requests_seconds_count{status="5xx", uri="/api/products"}→ monte
Anomalie F4 — Corruption silencieuse dans getProductById()¶
Niveau actif : Master (4) uniquement
Rupture de paradigme vs F1/F2/F3
F1, F2 et F3 génèrent des exceptions JVM réelles observables par APM : span rouge dans Tempo, méthode fautive visible dans Pyroscope, erreur HTTP 5xx. F4 est l'anti-thèse : aucune exception, HTTP 200, Tempo vert, heap stable, flamegraph inchangé. L'APM est structurellement aveugle à cette anomalie — le diagnostic passe obligatoirement par l'inspection du payload JSON (curl, DevTools, assertions de tests fonctionnels k6/Selenium).
Implémentation : FunctionalChaosService.applyF4DataCorruption() → appelé dans ProductService.getProductById()
// Niveau 0-3 : produit retourné tel quel depuis la BDD
// Niveau 4 : copie en mémoire avec 3 corruptions — BDD jamais modifiée
public Product applyF4DataCorruption(Product p) {
if (level.get() < 4) return p;
Product corrupted = new Product();
// F4-1 : prix × 1.5 (arrondi 2 décimales)
corrupted.setPrice(p.getPrice().multiply(new BigDecimal("1.5")).setScale(2, HALF_UP));
// F4-2 : stock forcé à 0 — produit affiché épuisé
corrupted.setStock(0);
// F4-3 : description tronquée à 30 caractères + marqueur
corrupted.setDescription(p.getDescription().substring(0, 30) + "[ERREUR: donnees tronquees]");
return corrupted; // HTTP 200 — aucune exception — Tempo vert
}
Impact visible :
GET /api/products/{id}retourne HTTP 200 avec les données corrompues- Aucune exception JVM — Tempo vert sur toutes les traces
- Aucun 5xx — les métriques d'erreur HTTP restent à zéro
- Le shop est cassé : prix gonflés ×1,5, tous les produits affichés "en rupture", descriptions tronquées
- La BDD est intacte — un
SELECTdirect confirme les vraies valeurs
Paradoxe pédagogique — signature APM unique
F4 est la seule anomalie où le monitoring est aveugle : Tempo vert, HTTP 200, heap stable, CPU normal, 0 erreur. La dégradation n'est visible que dans le payload JSON retourné au client. C'est exactement le cas réel d'un mapper défaillant, d'un cache empoisonné ou d'une sérialisation corrompue en production.
Ce que l'étudiant doit documenter
- Symptôme : prix anormaux dans le shop, produits épuisés, descriptions tronquées
- Détection :
curl https://perfshop-api.perfshop.io/api/products/1→ payload suspect - Confirmation : comparer le JSON API avec la BDD directe (
SELECT * FROM products WHERE id=1) - APM : toutes les traces sont vertes — le problème n'est pas dans l'infrastructure
- Point d'injection :
ProductService.getProductById()→applyF4DataCorruption() - Cause racine réelle (en production) : mapper O/R défaillant, cache Redis empoisonné, couche de sérialisation corrompue, DTO mapping incorrect
Métriques impactées :
http_server_requests_seconds_count{status="5xx"}→ 0 (c'est le piège)chaos_functional_f4_corruption→ compteur incrémenté à chaque appelgetProductById()en N4jvm_memory_used_bytes→ stableprocess_cpu_usage→ stable
Outils de détection :
# Comparer le prix API vs BDD directe
curl -s https://perfshop-api.perfshop.io/api/products/1 | jq '.price'
# → 44.99 (si prix BDD = 29.99 → corruption ×1.5 détectée)
# Vérifier stock API
curl -s https://perfshop-api.perfshop.io/api/products/1 | jq '.stock'
# → 0 (alors que BDD = 42)
Le chaos fonctionnel est la famille la plus directement observable dans Tempo.
Tableau de signatures¶
| Anomalie | Span en erreur | exception.type |
Durée du span | Spécificité |
|---|---|---|---|---|
| F1 NPE | POST /api/orders |
NullPointerException |
Courte | Crash immédiat dans processPayment |
| F2 SOE | POST /api/orders |
StackOverflowError |
Longue | Stacktrace tronqué |
| F3 OOM | GET /api/products |
OutOfMemoryError |
Variable | Heap JVM monte avant le crash |
| F4 | GET /api/products/{id} |
aucune | Normale | HTTP 200 — détection payload uniquement |
Étapes d'investigation dans Tempo¶
- Ouvrir le tableau Traces récentes dans le dashboard APM FORMATEUR ou APM ÉLÈVE
- Filtrer sur les traces en rouge (ERROR)
- Cliquer sur un Trace ID → waterfall des spans
- Identifier le span rouge — noter l'URI et la durée
- Déplier les attributs → chercher
exception.typeetexception.message - Lire
exception.stacktracepour identifier la méthode fautive
Astuce TraceQL
Filtrer uniquement les spans F1-F2-F3 dans la barre de recherche Tempo :
Pour F4, les traces sont vertes — filtrer sur l'endpoint impacté et comparer les payloads :Corrélation Pyroscope — F3 et le flamegraph heap¶
Le profil memory:alloc_objects de Pyroscope montre les allocations mémoire par méthode.
Quand F3 est active, FunctionalChaosService.applyF3CatalogOom apparaît comme un plateau
large dans le flamegraph heap — la méthode alloue massivement.
À comparer avec le flamegraph CPU (profil process_cpu) : F1 et F2 n'y apparaissent
pratiquement pas (crash rapide, peu de CPU consommé) alors que F3 consomme du CPU via le GC.
Observabilité¶
Chaque anomalie déclenchée produit :
- un log
WARNdans les logs du containerperfshop-app(préfixe[FunctionalChaos][F1]à[F4]) - un span en erreur dans Tempo avec
exception.typeetexception.stacktrace(F1/F2/F3 uniquement) - pour F4 : span vert, log
WARN [FunctionalChaos][F4], compteur Prometheuschaos_functional_f4_corruptionincrémenté - une métrique
http_server_requests_seconds_count{status="5xx"}incrémentée (F1/F2/F3 uniquement — F4 génère des 200)
Endpoint statut :
Réponse :
{
"level": 2,
"label": "Confirmé",
"activeAnomalies": ["F1", "F2"],
"f1Active": true,
"f2Active": true,
"f3Active": false
}
Contrôle admin¶
POST /api/admin/chaos/functional
{ "level": 1 } # 0 = off, 1 = Junior (F1), 2 = Confirmé (F1+F2), 3 = Expert (F1+F2+F3), 4 = Master (F1+F2+F3+F4)
Accessible depuis l'onglet ⚡ Chaos Fonctionnel du chaos-admin, via cookie de session
ou header X-Admin-Token.
Reset global :
Combinaisons pédagogiques recommandées¶
| Combinaison | Objectif pédagogique |
|---|---|
| F1 seul (niveau 1) | Identifier une NPE simple dans APM — exercice d'introduction |
| F1 + F2 (niveau 2) | Distinguer deux types d'exception sur le même endpoint /api/orders |
| F1 + F2 + F3 (niveau 3) | Corrélation multi-service : commande ET catalogue dégradés simultanément |
| F3 + Memory Leak backend | Comparer montée heap OOM (brutale) vs Memory Leak (linéaire) dans Grafana |
| F2 + Thread Pool backend | SOE crash + threads bloqués — dégradation complète du parcours commande |
Pertinence pédagogique¶
| Anomalie | Cause réelle en production |
|---|---|
| F1 NPE | Résultat de service externe (null) non testé avant usage — cas fréquent sur les intégrations paiement |
| F2 SOE | Calcul récursif de remise ou de frais sans condition d'arrêt — bug de régression classique |
| F3 OOM | FetchType.EAGER JPA sur une relation @OneToMany sans pagination — explosion mémoire sous charge |