# 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 1. Charger **GameLogicModule v1** - 100 entités actives (position, velocity) - State baseline enregistré 2. 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) 1. À t=10s, charger **GameLogicModule v2** en parallèle de v1 2. Configurer traffic split: - 90% → v1 (90 entités) - 10% → v2 (10 entités, canary) 3. 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 1. 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) 2. 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) 1. À t=90s, déclencher auto GC: - v1 inutilisée depuis 10s (0% traffic) - Seuil GC atteint 2. 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 1. À t=100s, simuler bug critique dans v2: - Exception dans collision detection - Ou: performance dégradée (lag) - Ou: corruption de state 2. Déclencher rollback automatique: - Recharger v1 en urgence - Restaurer state depuis backup 3. 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) 1. À t=120s, charger v3 (version optimisée) 2. 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) 3. 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 ```cpp // include/grove/IModule.h class IModule { public: // Méthodes existantes virtual void initialize(std::shared_ptr config) = 0; virtual void process(float deltaTime) = 0; virtual std::shared_ptr getState() const = 0; virtual void setState(std::shared_ptr state) = 0; virtual bool isIdle() const = 0; // Scenario 8: Config hot-reload virtual bool updateConfig(std::shared_ptr newConfig) { return false; } // Scenario 9: Dependencies virtual std::vector 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 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 ```cpp // tests/modules/GameLogicModuleV1.h #pragma once #include "grove/IModule.h" #include #include struct Entity { float x, y; // Position float vx, vy; // Velocity int id; }; class GameLogicModuleV1 : public IModule { public: void initialize(std::shared_ptr config) override; void process(float deltaTime) override; std::shared_ptr getState() const override; void setState(std::shared_ptr 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 entities_; int processCount_ = 0; }; extern "C" IModule* createModule() { return new GameLogicModuleV1(); } extern "C" void destroyModule(IModule* module) { delete module; } ``` ### GameLogicModule v2 ```cpp // tests/modules/GameLogicModuleV2.h #pragma once #include "grove/IModule.h" #include #include 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 config) override; void process(float deltaTime) override; std::shared_ptr getState() const override; void setState(std::shared_ptr 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 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 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 ```cpp // tests/modules/GameLogicModuleV3.h #pragma once #include "grove/IModule.h" #include #include 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 config) override; void process(float deltaTime) override; std::shared_ptr getState() const override; void setState(std::shared_ptr 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 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 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: ```cpp // include/grove/MultiVersionEngine.h #pragma once #include #include #include 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& versionWeights); // Get current traffic split std::map 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 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 ```cpp // src/MultiVersionEngine.cpp (excerpt) class TrafficRouter { public: // Distribute entities across versions based on weights std::map> routeTraffic( const std::vector& entities, const std::map& weights) { std::map> 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 ```cpp // 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 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 1. ✅ Chargement de 3 versions du même module simultanément 2. ✅ Traffic routing précis (±2% de la cible) pour canary et 3-way split 3. ✅ Migration de state progressive sans perte de données 4. ✅ Rollback en < 200ms avec state restauré 5. ✅ Isolation mémoire complète (aucune corruption croisée) 6. ✅ GC automatique des versions inutilisées après seuil 7. ✅ Total entités constant (100) durant toutes les phases 8. ✅ Aucun crash durant tout le test ### NICE TO HAVE 1. ✅ Hot-patch d'une version sans full reload (micro-update) 2. ✅ A/B testing metrics (compare perf v1 vs v2 vs v3) 3. ✅ Version pinning (certaines entités forcées sur version spécifique) 4. ✅ Circuit breaker (auto-rollback si error rate > 5%) 5. ✅ Canary promotion automatique si metrics OK après 30s --- ## 🔧 API Nécessaires dans DebugEngine ### Nouvelles méthodes ```cpp 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& weights); std::map 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 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: 1. Deploy nouveau système PvP sur 5% (5k joueurs) 2. Monitor crash rate, lag, feedback 3. Si OK → gradual rollout 10% → 30% → 100% 4. Si bug critique → instant rollback en < 1s 5. Zero downtime, minimal risk --- **Prochaine étape**: Implémentation