Aller au contenu

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 500 avec le message NullPointerException
  • Le span POST /api/orders apparaît en rouge (ERROR) dans Tempo
  • L'attribut exception.type = java.lang.NullPointerException est visible dans les détails du span
  • L'attribut exception.stacktrace pointe sur FunctionalChaosService.applyF1PaymentNpe()

Ce que l'étudiant doit documenter

  1. Symptôme : 100% des commandes échouent en HTTP 500
  2. Cause : NullPointerException dans la méthode de paiement
  3. Méthode fautive : FunctionalChaosService.applyF1PaymentNpe() appelée depuis processPaymentPublic()
  4. 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 500 et le message StackOverflowError
  • 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

  1. Symptôme : HTTP 500 au moment du calcul du total (avant le paiement)
  2. Cause : StackOverflowError — récursion infinie détectée par la JVM
  3. Distinction F1 vs F2 : la SOE survient avant le paiement (F1 survient pendant le paiement)
  4. 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"} → monte
  • jvm_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.OutOfMemoryError sur les spans GET /api/products

Ce que l'étudiant doit documenter

  1. Symptôme : page catalogue inaccessible (HTTP 500), heap JVM en croissance rapide
  2. Cause : OutOfMemoryError — allocation massive non libérable (boucle infinie de blocs 8 Mo)
  3. Corrélation : observer simultanément le flamegraph Pyroscope — applyF3CatalogOom apparaît en large plateau dans le flamegraph heap (profil memory:alloc_objects). La boucle dure ~2-4s avant OOM, ce qui laisse le temps au profiler de l'échantillonner.
  4. Cause racine réelle (en production) : ORM chargement FetchType.EAGER sur collection massive, ou cache non borné

Métriques impactées :

  • jvm_memory_used_bytes{area="heap"} → montée rapide et non récupérée
  • jvm_gc_pause_seconds_sum → GC s'emballe avant le crash
  • http_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 SELECT direct 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

  1. Symptôme : prix anormaux dans le shop, produits épuisés, descriptions tronquées
  2. Détection : curl https://perfshop-api.perfshop.io/api/products/1 → payload suspect
  3. Confirmation : comparer le JSON API avec la BDD directe (SELECT * FROM products WHERE id=1)
  4. APM : toutes les traces sont vertes — le problème n'est pas dans l'infrastructure
  5. Point d'injection : ProductService.getProductById()applyF4DataCorruption()
  6. 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 appel getProductById() en N4
  • jvm_memory_used_bytes → stable
  • process_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

  1. Ouvrir le tableau Traces récentes dans le dashboard APM FORMATEUR ou APM ÉLÈVE
  2. Filtrer sur les traces en rouge (ERROR)
  3. Cliquer sur un Trace ID → waterfall des spans
  4. Identifier le span rouge — noter l'URI et la durée
  5. Déplier les attributs → chercher exception.type et exception.message
  6. Lire exception.stacktrace pour identifier la méthode fautive

Astuce TraceQL

Filtrer uniquement les spans F1-F2-F3 dans la barre de recherche Tempo :

{span.exception.type =~ "NullPointerException|StackOverflowError|OutOfMemoryError"}
Pour F4, les traces sont vertes — filtrer sur l'endpoint impacté et comparer les payloads :
{span.http.route = "/api/products/{id}" && duration > 0ms}


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 WARN dans les logs du container perfshop-app (préfixe [FunctionalChaos][F1] à [F4])
  • un span en erreur dans Tempo avec exception.type et exception.stacktrace (F1/F2/F3 uniquement)
  • pour F4 : span vert, log WARN [FunctionalChaos][F4], compteur Prometheus chaos_functional_f4_corruption incré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 :

GET /api/chaos/public/functional/status

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 :

POST /api/admin/chaos/reset


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