Added three new integration test scenarios: - Test 08: Config Hot-Reload (dynamic configuration updates) - Test 09: Module Dependencies (dependency injection & cascade reload) - Test 10: Multi-Version Coexistence (canary deployment & progressive migration) Fixes: - Fixed CTest working directory for all tests (add WORKING_DIRECTORY) - Fixed module paths to use relative paths (./ prefix) - Fixed IModule.h comments for clarity New test modules: - ConfigurableModule (for config reload testing) - BaseModule, DependentModule, IndependentModule (for dependency testing) - GameLogicModuleV1/V2/V3 (for multi-version testing) Test coverage now includes 10 comprehensive integration scenarios covering hot-reload, chaos testing, stress testing, race conditions, memory leaks, error recovery, limits, config reload, dependencies, and multi-versioning. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
26 KiB
Scénario 10: Multi-Version Module Coexistence
Priorité: ⭐⭐ HIGH Phase: 3 (ADVANCED) Durée estimée: ~2-3 minutes Effort implémentation: ~6-8 heures
🎯 Objectif
Valider que le système peut gérer plusieurs versions d'un même module chargées simultanément en production, avec:
- Chargement de multiples versions (v1, v2, v3) du même module en parallèle
- Canary deployment (routage progressif du traffic vers nouvelle version)
- Migration progressive du state entre versions
- Rollback instantané en cas de problème
- Isolation mémoire complète entre versions (pas de corruption croisée)
- Garbage collection automatique des versions inutilisées
📋 Description
Cas d'Usage Réel
Dans un jeu en production, ce scénario permet de:
- Zero-downtime deployments: Déployer nouveau système de combat sans redémarrer serveurs
- Canary releases: Tester optimisation sur 10% des joueurs avant rollout complet
- A/B testing: Comparer performance v1 vs v2 en production avec métriques
- Instant rollback: Revenir à version stable en < 1s si bugs critiques
- Blue/Green deployment: Basculer instantanément entre versions
Architecture des Modules de Test
GameLogicModule v1: Version baseline
- Logic simple: position update (x += vx * dt, y += vy * dt)
- 100 entités actives
- Pas de collision detection
- Version = 1
GameLogicModule v2: Version avec nouvelle feature
- Logic v1 + collision detection (nouvelle feature)
- Compatibilité state avec v1
- Version = 2
GameLogicModule v3: Version optimisée
- Physics system optimisé (SIMD, spatial hashing)
- Collision detection améliorée
- Version = 3
Setup Initial (Phase 0)
Durée: 10 secondes
- Charger GameLogicModule v1
- 100 entités actives (position, velocity)
- State baseline enregistré
- Vérifier stabilité:
- ✅ v1 traite 100% des entités
- ✅ Aucune erreur
- ✅ Performance baseline établie
Phase 1: Canary Deployment v2
Durée: 30 secondes
Objectif: Tester v2 sur 10% du traffic (canary)
- À t=10s, charger GameLogicModule v2 en parallèle de v1
- Configurer traffic split:
- 90% → v1 (90 entités)
- 10% → v2 (10 entités, canary)
- Vérifier isolation:
- ✅ v1 traite exactement 90 entités
- ✅ v2 traite exactement 10 entités
- ✅ Total = 100 entités (aucune perdue)
- ✅ State complètement isolé (pas de corruption)
- ✅ Canary accuracy: 10% ±2%
Métriques:
version_load_time_ms: Temps de chargement de v2 (< 200ms)canary_accuracy: Précision du routage (10% ±2%)isolation_check: Aucune corruption mémoire croisée
Phase 2: Progressive Migration v1 → v2
Durée: 40 secondes
Objectif: Migration graduelle du traffic vers v2
-
Migration progressive du traffic:
- t=50s: 30% v2, 70% v1 (30 entités → v2)
- t=60s: 50% v2, 50% v1 (50 entités → v2)
- t=70s: 80% v2, 20% v1 (80 entités → v2)
- t=80s: 100% v2, 0% v1 (migration complète)
-
Vérifier migration:
- ✅ State migré progressivement (pas de perte)
- ✅ Chaque étape: total = 100 entités
- ✅ Performance stable durant migration
- ✅ Aucune corruption de données
Métriques:
state_migration_time_ms: Temps pour migrer state v1→v2 (< 500ms)migration_data_loss: Aucune entité perdue (0)traffic_accuracy: Précision du routage à chaque étape (±2%)
Phase 3: Garbage Collection v1
Durée: 10 secondes
Objectif: Décharger automatiquement v1 (plus utilisée)
- À t=90s, déclencher auto GC:
- v1 inutilisée depuis 10s (0% traffic)
- Seuil GC atteint
- Vérifier déchargement:
- ✅ v1 déchargée automatiquement
- ✅ v2 continue sans interruption (100 entités)
- ✅ Mémoire libérée (> 95% de la mémoire v1)
Métriques:
gc_trigger_time_s: Temps avant GC après inutilisation (10s)gc_efficiency: % mémoire libérée (> 95%)gc_time_ms: Temps pour GC complète (< 100ms)
Phase 4: Emergency Rollback v2 → v1
Durée: 20 secondes
Objectif: Rollback instantané si v2 plante
- À t=100s, simuler bug critique dans v2:
- Exception dans collision detection
- Ou: performance dégradée (lag)
- Ou: corruption de state
- Déclencher rollback automatique:
- Recharger v1 en urgence
- Restaurer state depuis backup
- Vérifier rollback:
- ✅ v1 rechargée en < 200ms
- ✅ State restauré depuis backup (aucune perte)
- ✅ Service continuity (downtime < 1s)
- ✅ 100 entités actives
Métriques:
rollback_time_ms: Temps total de rollback (< 200ms)rollback_downtime_ms: Temps sans service (< 1000ms)state_recovery: State restauré complètement (100%)
Phase 5: Three-Way Coexistence (v1, v2, v3)
Durée: 30 secondes
Objectif: 3 versions en parallèle (A/B/C testing)
- À t=120s, charger v3 (version optimisée)
- Configurer traffic split à 3 voies:
- 20% → v1 (20 entités, baseline)
- 30% → v2 (30 entités, feature test)
- 50% → v3 (50 entités, optimized test)
- Vérifier coexistence:
- ✅ 3 versions actives simultanément
- ✅ Total = 100 entités (aucune perdue)
- ✅ Isolation mémoire stricte (3 heaps séparés)
- ✅ Routage correct du traffic (±2%)
- ✅ Métriques séparées par version
Métriques:
multi_version_count: Nombre de versions actives (3)traffic_split_accuracy: Précision du routage 3-voies (±2%)memory_isolation: Aucune corruption croisée (100%)performance_comparison: Métriques v1 vs v2 vs v3
🏗️ Implémentation
Extension de IModule.h
// include/grove/IModule.h
class IModule {
public:
// Méthodes existantes
virtual void initialize(std::shared_ptr<IDataNode> config) = 0;
virtual void process(float deltaTime) = 0;
virtual std::shared_ptr<IDataNode> getState() const = 0;
virtual void setState(std::shared_ptr<IDataNode> state) = 0;
virtual bool isIdle() const = 0;
// Scenario 8: Config hot-reload
virtual bool updateConfig(std::shared_ptr<IDataNode> newConfig) {
return false;
}
// Scenario 9: Dependencies
virtual std::vector<std::string> getDependencies() const {
return {};
}
// Scenario 9-10: Version tracking
virtual int getVersion() const {
return 1;
}
// NOUVELLE: Scenario 10 - State migration
virtual bool migrateStateFrom(int fromVersion,
std::shared_ptr<IDataNode> oldState) {
// Default: simple copy if same version
if (fromVersion == getVersion()) {
setState(oldState);
return true;
}
return false; // Override for custom migration
}
virtual ~IModule() = default;
};
GameLogicModule v1
// tests/modules/GameLogicModuleV1.h
#pragma once
#include "grove/IModule.h"
#include <vector>
#include <memory>
struct Entity {
float x, y; // Position
float vx, vy; // Velocity
int id;
};
class GameLogicModuleV1 : public IModule {
public:
void initialize(std::shared_ptr<IDataNode> config) override;
void process(float deltaTime) override;
std::shared_ptr<IDataNode> getState() const override;
void setState(std::shared_ptr<IDataNode> state) override;
bool isIdle() const override { return true; }
int getVersion() const override { return 1; }
// v1: Simple movement only
void updateEntities(float dt) {
for (auto& e : entities_) {
e.x += e.vx * dt;
e.y += e.vy * dt;
}
}
size_t getEntityCount() const { return entities_.size(); }
private:
std::vector<Entity> entities_;
int processCount_ = 0;
};
extern "C" IModule* createModule() {
return new GameLogicModuleV1();
}
extern "C" void destroyModule(IModule* module) {
delete module;
}
GameLogicModule v2
// tests/modules/GameLogicModuleV2.h
#pragma once
#include "grove/IModule.h"
#include <vector>
#include <memory>
struct Entity {
float x, y;
float vx, vy;
int id;
bool collided; // NEW in v2
};
class GameLogicModuleV2 : public IModule {
public:
void initialize(std::shared_ptr<IDataNode> config) override;
void process(float deltaTime) override;
std::shared_ptr<IDataNode> getState() const override;
void setState(std::shared_ptr<IDataNode> state) override;
bool isIdle() const override { return true; }
int getVersion() const override { return 2; }
// v2: Movement + collision detection
void updateEntities(float dt) {
for (auto& e : entities_) {
e.x += e.vx * dt;
e.y += e.vy * dt;
checkCollisions(e); // NEW feature in v2
}
}
// State migration from v1
bool migrateStateFrom(int fromVersion,
std::shared_ptr<IDataNode> oldState) override {
if (fromVersion == 1) {
// Migrate v1 → v2: add collided flag
setState(oldState);
for (auto& e : entities_) {
e.collided = false; // Initialize new field
}
return true;
}
return false;
}
size_t getEntityCount() const { return entities_.size(); }
private:
std::vector<Entity> entities_;
int processCount_ = 0;
void checkCollisions(Entity& e) {
// Simple collision detection
e.collided = (e.x < 0 || e.x > 1000 || e.y < 0 || e.y > 1000);
}
};
extern "C" IModule* createModule() {
return new GameLogicModuleV2();
}
extern "C" void destroyModule(IModule* module) {
delete module;
}
GameLogicModule v3
// tests/modules/GameLogicModuleV3.h
#pragma once
#include "grove/IModule.h"
#include <vector>
#include <memory>
struct Entity {
float x, y;
float vx, vy;
int id;
bool collided;
float mass; // NEW in v3 for advanced physics
};
class GameLogicModuleV3 : public IModule {
public:
void initialize(std::shared_ptr<IDataNode> config) override;
void process(float deltaTime) override;
std::shared_ptr<IDataNode> getState() const override;
void setState(std::shared_ptr<IDataNode> state) override;
bool isIdle() const override { return true; }
int getVersion() const override { return 3; }
// v3: Optimized physics + advanced collision
void updateEntities(float dt) {
for (auto& e : entities_) {
applyPhysics(e, dt); // OPTIMIZED in v3
checkCollisions(e);
}
}
// State migration from v2
bool migrateStateFrom(int fromVersion,
std::shared_ptr<IDataNode> oldState) override {
if (fromVersion == 2 || fromVersion == 1) {
setState(oldState);
for (auto& e : entities_) {
e.mass = 1.0f; // Initialize new field
}
return true;
}
return false;
}
size_t getEntityCount() const { return entities_.size(); }
private:
std::vector<Entity> entities_;
int processCount_ = 0;
void applyPhysics(Entity& e, float dt) {
// Advanced physics (SIMD optimized in real impl)
float ax = 0.0f, ay = 9.8f; // Gravity
e.vx += ax * dt;
e.vy += ay * dt;
e.x += e.vx * dt;
e.y += e.vy * dt;
}
void checkCollisions(Entity& e) {
e.collided = (e.x < 0 || e.x > 1000 || e.y < 0 || e.y > 1000);
}
};
extern "C" IModule* createModule() {
return new GameLogicModuleV3();
}
extern "C" void destroyModule(IModule* module) {
delete module;
}
MultiVersionEngine API
Extension du système pour gérer plusieurs versions simultanément:
// include/grove/MultiVersionEngine.h
#pragma once
#include <string>
#include <map>
#include <memory>
class MultiVersionEngine {
public:
// Load specific version of a module
bool loadModuleVersion(const std::string& moduleName,
int version,
const std::string& libraryPath);
// Unload specific version
bool unloadModuleVersion(const std::string& moduleName, int version);
// Traffic routing (canary deployment)
// Example: setTrafficSplit("GameLogic", {{1, 0.9}, {2, 0.1}})
// → 90% traffic to v1, 10% to v2
bool setTrafficSplit(const std::string& moduleName,
const std::map<int, float>& versionWeights);
// Get current traffic split
std::map<int, float> getTrafficSplit(const std::string& moduleName) const;
// State migration between versions
bool migrateState(const std::string& moduleName,
int fromVersion,
int toVersion);
// Emergency rollback to specific version
bool rollback(const std::string& moduleName,
int targetVersion,
float maxDowntimeMs = 1000.0f);
// Automatic garbage collection of unused versions
void enableAutoGC(const std::string& moduleName,
float unusedThresholdSeconds = 60.0f);
void disableAutoGC(const std::string& moduleName);
// Get all loaded versions for a module
std::vector<int> getLoadedVersions(const std::string& moduleName) const;
// Check if version is loaded
bool isVersionLoaded(const std::string& moduleName, int version) const;
// Get metrics for specific version
struct VersionMetrics {
int version;
float trafficPercent;
size_t processedEntities;
float avgProcessTimeMs;
size_t memoryUsageBytes;
float uptimeSeconds;
};
VersionMetrics getVersionMetrics(const std::string& moduleName,
int version) const;
};
Traffic Router Implementation
// src/MultiVersionEngine.cpp (excerpt)
class TrafficRouter {
public:
// Distribute entities across versions based on weights
std::map<int, std::vector<Entity>> routeTraffic(
const std::vector<Entity>& entities,
const std::map<int, float>& weights) {
std::map<int, std::vector<Entity>> routed;
// Validate weights sum to 1.0
float sum = 0.0f;
for (auto& [v, w] : weights) sum += w;
assert(std::abs(sum - 1.0f) < 0.01f);
// Deterministic routing based on entity ID
for (const auto& e : entities) {
float hash = (e.id % 100) / 100.0f; // [0, 1)
float cumulative = 0.0f;
for (auto& [version, weight] : weights) {
cumulative += weight;
if (hash < cumulative) {
routed[version].push_back(e);
break;
}
}
}
return routed;
}
};
State Migration Helper
// Helper for progressive state migration
class StateMigrator {
public:
// Migrate state progressively from v1 to v2
bool migrate(IModule* fromModule, IModule* toModule) {
// Extract state from old version
auto oldState = fromModule->getState();
// Attempt migration
bool success = toModule->migrateStateFrom(
fromModule->getVersion(),
oldState
);
if (success) {
recordMigration(fromModule->getVersion(),
toModule->getVersion());
}
return success;
}
private:
void recordMigration(int from, int to) {
migrationHistory_.push_back({from, to, std::chrono::steady_clock::now()});
}
struct MigrationRecord {
int fromVersion;
int toVersion;
std::chrono::steady_clock::time_point timestamp;
};
std::vector<MigrationRecord> migrationHistory_;
};
📊 Métriques Collectées
| Métrique | Description | Seuil |
|---|---|---|
| version_load_time_ms | Temps de chargement d'une nouvelle version | < 200ms |
| canary_accuracy | Précision du routage canary (10% = 10.0% ±2%) | ±2% |
| traffic_split_accuracy | Précision du routage multi-version | ±2% |
| state_migration_time_ms | Temps de migration de state entre versions | < 500ms |
| rollback_time_ms | Temps de rollback complet | < 200ms |
| rollback_downtime_ms | Downtime durant rollback | < 1000ms |
| memory_isolation | Isolation mémoire entre versions (corruption) | 100% |
| gc_trigger_time_s | Temps avant GC après inutilisation | 10s |
| gc_efficiency | % mémoire libérée après GC | > 95% |
| multi_version_count | Nombre max de versions simultanées | 3 |
| total_entities | Total entités (doit rester constant) | 100 |
✅ Critères de Succès
MUST PASS
- ✅ Chargement de 3 versions du même module simultanément
- ✅ Traffic routing précis (±2% de la cible) pour canary et 3-way split
- ✅ Migration de state progressive sans perte de données
- ✅ Rollback en < 200ms avec state restauré
- ✅ Isolation mémoire complète (aucune corruption croisée)
- ✅ GC automatique des versions inutilisées après seuil
- ✅ Total entités constant (100) durant toutes les phases
- ✅ Aucun crash durant tout le test
NICE TO HAVE
- ✅ Hot-patch d'une version sans full reload (micro-update)
- ✅ A/B testing metrics (compare perf v1 vs v2 vs v3)
- ✅ Version pinning (certaines entités forcées sur version spécifique)
- ✅ Circuit breaker (auto-rollback si error rate > 5%)
- ✅ Canary promotion automatique si metrics OK après 30s
🔧 API Nécessaires dans DebugEngine
Nouvelles méthodes
class DebugEngine {
public:
// Méthodes existantes...
// NOUVELLES pour Scenario 10
bool loadModuleVersion(const std::string& moduleName,
int version,
const std::string& path);
bool unloadModuleVersion(const std::string& moduleName, int version);
bool setTrafficSplit(const std::string& moduleName,
const std::map<int, float>& weights);
std::map<int, float> getTrafficSplit(const std::string& moduleName);
bool migrateState(const std::string& moduleName,
int fromVersion,
int toVersion);
bool rollback(const std::string& moduleName, int targetVersion);
void enableAutoGC(const std::string& moduleName, float thresholdSec);
std::vector<int> getLoadedVersions(const std::string& moduleName);
MultiVersionEngine::VersionMetrics getVersionMetrics(
const std::string& moduleName,
int version);
};
🐛 Cas d'Erreur Attendus
| Erreur | Cause | Action |
|---|---|---|
| Traffic routing imprécis (15% au lieu de 10%) | Mauvais algorithme de distribution | FAIL - fix router |
| Entités perdues (95 au lieu de 100) | Migration incomplète | FAIL - fix migration |
| Corruption mémoire entre versions | Heap partagé | FAIL - isoler complètement |
| Rollback > 1s | Rechargement lent | FAIL - optimiser load |
| v1 pas GC après 60s inutilisée | Auto GC non implémenté | FAIL - implémenter GC |
| Crash lors de 3-way split | Race condition | FAIL - fix synchronisation |
| State migration v1→v2 perd données | Migration partielle | FAIL - vérifier migration |
📝 Output Attendu
================================================================================
TEST: Multi-Version Module Coexistence
================================================================================
=== Phase 0: Setup Baseline (10s) ===
✓ GameLogicModule v1 loaded
✓ 100 entities initialized
✓ Baseline performance: 0.5ms/frame
✓ Baseline memory: 8.2MB
=== Phase 1: Canary Deployment v2 (30s) ===
Loading GameLogicModule v2...
✓ v2 loaded in 145ms
Configuring canary: 10% v2, 90% v1
Traffic distribution:
v1: 90 entities (90.0%) ✓
v2: 10 entities (10.0%) ✓
Total: 100 entities ✓
✓ Canary accuracy: 10.0% (±2%)
✓ Memory isolation: 100% (no corruption)
✓ Both versions stable
Metrics:
Version load time: 145ms ✓ (< 200ms)
Canary accuracy: 10.0% ✓ (±2%)
Memory isolation: 100% ✓
=== Phase 2: Progressive Migration v1 → v2 (40s) ===
t=50s: Traffic split → 30% v2, 70% v1
v1: 70 entities, v2: 30 entities ✓
t=60s: Traffic split → 50% v2, 50% v1
v1: 50 entities, v2: 50 entities ✓
t=70s: Traffic split → 80% v2, 20% v1
v1: 20 entities, v2: 80 entities ✓
t=80s: Traffic split → 100% v2, 0% v1 (migration complete)
v1: 0 entities, v2: 100 entities ✓
✓ State migrated progressively (no data loss)
✓ Total entities constant: 100
✓ Performance stable
Metrics:
State migration time: 385ms ✓ (< 500ms)
Data loss: 0% ✓
Migration steps: 4 ✓
=== Phase 3: Garbage Collection v1 (10s) ===
v1 unused for 10s → triggering auto GC...
✓ v1 unloaded automatically
✓ v2 continues without interruption (100 entities)
✓ Memory freed: 7.8MB (95.1%)
Metrics:
GC trigger time: 10.0s ✓
GC efficiency: 95.1% ✓ (> 95%)
GC time: 78ms ✓ (< 100ms)
=== Phase 4: Emergency Rollback v2 → v1 (20s) ===
Simulating critical bug in v2 (collision detection crash)...
⚠️ v2 error detected → triggering emergency rollback
Rollback sequence:
1. State backup captured
2. Reloading v1...
3. State restored from backup
4. Traffic redirected to v1
✓ v1 reloaded in 178ms
✓ State restored (100 entities)
✓ Downtime: 892ms (< 1s)
✓ Service continuity maintained
Metrics:
Rollback time: 178ms ✓ (< 200ms)
Rollback downtime: 892ms ✓ (< 1000ms)
State recovery: 100% ✓
=== Phase 5: Three-Way Coexistence (v1, v2, v3) (30s) ===
Loading GameLogicModule v3 (optimized)...
✓ v3 loaded in 152ms
Configuring 3-way traffic split: 20% v1, 30% v2, 50% v3
Traffic distribution:
v1: 20 entities (20.0%) ✓
v2: 30 entities (30.0%) ✓
v3: 50 entities (50.0%) ✓
Total: 100 entities ✓
✓ 3 versions coexisting
✓ Memory isolation: 100%
✓ Traffic routing accurate (±2%)
Performance comparison:
v1: 0.50ms/frame (baseline)
v2: 0.68ms/frame (+36% due to collision)
v3: 0.42ms/frame (-16% optimized!)
Metrics:
Multi-version count: 3 ✓
Traffic accuracy: ±1.5% ✓ (< ±2%)
Memory isolation: 100% ✓
Total entities: 100 ✓
================================================================================
METRICS SUMMARY
================================================================================
Version load time: 152ms (threshold: < 200ms) ✓
Canary accuracy: 10.0% (target: 10% ±2%) ✓
Traffic split accuracy: ±1.5% (threshold: ±2%) ✓
State migration time: 385ms (threshold: < 500ms) ✓
Rollback time: 178ms (threshold: < 200ms) ✓
Rollback downtime: 892ms (threshold: < 1000ms) ✓
Memory isolation: 100% (threshold: 100%) ✓
GC efficiency: 95.1% (threshold: > 95%) ✓
Multi-version count: 3 (target: 3) ✓
Total entities: 100 (constant) ✓
================================================================================
ASSERTIONS
================================================================================
✓ multi_version_loading (3 versions loaded)
✓ canary_deployment_accurate
✓ progressive_migration_no_loss
✓ rollback_fast_and_safe
✓ memory_isolation_complete
✓ auto_gc_triggered
✓ three_way_coexistence
✓ traffic_routing_precise
✓ total_entities_constant
✓ no_crashes
Result: ✅ PASSED
================================================================================
📅 Planning
Jour 13 (3h):
- Étendre IModule avec
migrateStateFrom() - Implémenter GameLogicModuleV1, V2, V3
- Créer Entity struct et logic de base
Jour 14 (3h):
- Implémenter MultiVersionEngine class
- Implémenter TrafficRouter (canary, multi-way split)
- Implémenter StateMigrator
Jour 15 (2h):
- Implémenter Auto GC system
- Implémenter Emergency Rollback
- Ajouter métriques par version
Jour 16 (2h):
- Implémenter test_10_multiversion_coexistence.cpp
- Debug + validation
- Intégration CMake
🎯 Valeur Ajoutée
Ce test valide une fonctionnalité critique pour la production :
Zero-Downtime Deployments
- Déployer nouveau système de combat sans arrêter serveurs
- Migration progressive pour minimiser risques
- Rollback instantané si problème
Canary Releases
- Tester nouvelle version sur 5-10% des joueurs d'abord
- Comparer métriques (perf, bugs) avant rollout complet
- Réduire blast radius si bugs critiques
A/B Testing en Production
- Comparer performance v1 vs v2 vs v3 avec vraies données
- Décisions data-driven pour optimisations
- Validation de nouvelles features avant déploiement global
Business Value
- Réduction des downtimes : 0s au lieu de 5-10min par déploiement
- Réduction des risques : Canary détecte bugs avant rollout complet
- Time-to-market : Déploiements plus fréquents et plus sûrs
- Player experience : Aucune interruption de service
Exemple Réel
Dans un MMO avec 100k joueurs connectés:
- Deploy nouveau système PvP sur 5% (5k joueurs)
- Monitor crash rate, lag, feedback
- Si OK → gradual rollout 10% → 30% → 100%
- Si bug critique → instant rollback en < 1s
- Zero downtime, minimal risk
Prochaine étape: Implémentation