Documentation Migration MC1 vers MC2

Documentation Migration MC1 vers MC2

Présentation générale

Objectif

Cette procédure migre le système de gestion des assets média (DAM - Digital Asset Management) depuis une architecture MediaCloud 1 (MC1) vers MediaCloud 2 (MC2). Cette migration transforme un stockage principalement local (SAN) vers une architecture cloud native basée sur AWS S3 avec traitement asynchrone et ingestion automatique.

Points d'entrée

  • Ecran admin:

    image-20260430-065236.png
  • Classe principale : MigrateProcess (war/src/main/java/com/wedia/dam/mediacloud/api/migrationmc2/MigrateProcess.java)

  • Contrôleur : MigrateController (war/src/main/java/com/wedia/dam/mediacloud/api/migrationmc2/MigrateController.java)


Pré-requis

  • Moteur en 2026.2.1 au minimum

  • Configuration MC2 effectuée sur tous les noeuds du cluster

  • Configuration de ImagingServer pour faire des preview en 10.000x10.000

  • Analyse des presets existants

  • Analyse du code applicatif


Architecture et flux de données

Composants impliqués

1. Base de données

Rôle : Stockage des métadonnées des assets et structure des objets

  • Lecture :

    • Structure des objets (CTObjectStructure)

    • Instances d'objets avec propriétés file ou image

    • Configuration EVP (vidéos) via OPV2Database

  • Écriture (Phase 3) :

    • Modification du type de champ file/image → blob

    • Mise à jour de la structure persistante

2. Stockage SAN (Storage Area Network)

Rôle : Stockage local historique des fichiers sources et cache

  • Lecture :

    • Fichiers sources originaux (file/objecttype/filename)

    • Cache de variations pré-calculées (thumbnails, presets)

    • Fichiers temporaires de workcopy

  • Écriture : Aucune pendant la migration

  • Suppression (Phase 5) : Nettoyage des fichiers locaux après migration réussie

3. Imaging Server

Rôle : Service de traitement d'images en mémoire

  • Opérations :

    • Génération de thumbnails (différentes résolutions)

    • Création de workcopy (10000×10000 pixels)

    • Conversion de formats (PNG, JPG, WebP)

    • Génération d'images animées (GIF, WebP)

    • Calcul de hashimage pour recherche visuelle

  • Accès : Via ImagingFactory.getImaging()

4. MC2 / AWS S3

Rôle : 

  • Stockage des fichiers sources originaux en cloud

  • Stockage des variations et résultats de traitement

  • Opérations :

    • PUT : Upload des sources (Phase 1, Phase 2)

    • GET metadata : Vérification existence/taille

    • DELETE : Suppression des anciens chemins MC1.5 (Phase 4)

5. AWS DynamoDB - MigrateLog

Rôle : Journal des modifications pendant la migration

  • Table : {clientAppName}.migratelog-table

  • Cycle de vie :

    • Activé au début de Phase 1

    • Alimenté par les hooks CRUD pendant Phases 1-2

    • Lu et vidé pendant Phase 2 et Phase 4

    • Désactivé en Phase 3

6. AWS DynamoDB - MigrateState

Rôle : État de progression par asset

  • Table : {clientAppName}.migratestate-table

7. Azure Blob Storage (OPV2)

Rôle : Stockage historique des vidéos HLS et sous-titres

  • Accès : Via OPV2Database.GetSecretConfig() et AzureStorage

  • Lecture seule :

    • Fichiers HLS (.m3u8.ts)

    • Sous-titres (.vtt)

  • Utilisé dans : Phase 1 pour copyVideoHls() et copyVideoSubtitle()

8. Thread Pool Migration

Rôle : Parallélisation des traitements

  • Configuration : ThreadPoolExecutorWrapper nommé "WXM_DAMDY.MigrateMc2"

  • Taille : Configurable via config.nbrThread (défaut: nbrCPU/2)

  • Utilisation :

    • Phase 1 : Traitement parallèle des objets

    • Phase 2 : Traitement parallèle des logs

    • Phase 5 : Suppression parallèle des fichiers locaux


Diagrammes d'architecture réseau

Vue d'ensemble des échanges réseau

┌──────────────────────────────────────────────────┐ │ Serveur Tomcat (WEDIAG) │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ MigrateProcess │ │ │ │ (war/WEB-INF/classes) │ │ │ └──────────┬──────────────────────────┘ │ │ │ │ │ ├─────────────────────┐ │ │ │ │ │ │ ┌──────────▼──────────┐ ┌─────▼──────────┐ │ │ │ AwsCloudService │ │ ImagingFactory│ │ │ │ (HTTP Client) │ │ │ │ │ └──────────┬──────────┘ └───────┬────────┘ │ └─────────────┼──────────────────────┼─────────────┘ │ │ │ │ ┌─────────────────────────┼──────────────────────┼──────────────────┐ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ┌────────────────┐ ┌──────────────────┐ ┌──────────────┐ ┌────────────────┐ │ Base SQL │ │ AWS Cloud │ │ Imaging │ │ Azure Blob │ │ (MySQL/ │ │ │ │ Server │ │ Storage │ │ PostgreSQL) │ │ ┌────────────┐ │ │ (Docker) │ │ (OPV2) │ │ │ │ │ S3 Sources │ │ │ │ │ │ │ - Metadata │ │ │ Bucket │ │ │ - Thumbnail │ │ - HLS (.m3u8) │ │ - Structures │ │ └────────────┘ │ │ - Workcopy │ │ - Segments │ │ - Config EVP │ │ │ │ - Convert │ │ (.ts) │ │ │ │ ┌────────────┐ │ │ - HashImage │ │ - Subtitles │ └────────────────┘ │ │ S3 Results │ │ │ │ │ (.vtt) │ │ │ Bucket │ │ └──────────────┘ └────────────────┘ │ └────────────┘ │ │ │ │ ┌────────────┐ │ │ │ DynamoDB │ │ │ │ │ │ │ │ - Migrate │ │ │ │ Log │ │ │ │ - Migrate │ │ │ │ State │ │ │ └────────────┘ │ └──────────────────┘ ┌────────────────────────────────────────────────────────────────────────┐ │ SAN (Storage Area Network) - Accès local filesystem │ │ │ │ /san/file/{objecttype}/{filename} ← Lecture sources │ │ /san/cache/{hash}/thumbnail_*.{format} ← Lecture cache variations │ │ │ │ Accès : NFS / CIFS / Direct mount │ └────────────────────────────────────────────────────────────────────────┘

Flux réseau par phase de migration

Phase 1 : Copie initiale

┌─────────────┐ │ Tomcat │ │ (Thread 1) │ └──────┬──────┘ │ 1. SELECT structure, instances ├─────────────────────────────────────► ┌──────────────┐ │ │ MySQL/ │ │◄───────────────────────────────────────┤ PostgreSQL │ │ ResultSet (metadata) └──────────────┘ │ 2. Read file source ├─────────────────────────────────────► ┌──────────────┐ │ │ SAN (local) │ │◄───────────────────────────────────────┤ │ │ Binary data (File InputStream) └──────────────┘ │ 3. POST getThumbnail(params) ├─────────────────────────────────────► ┌──────────────┐ │ │ Imaging │ │◄───────────────────────────────────────┤ Server │ │ Response: path to generated file │ (HTTP API) │ │ └──────────────┘ │ 4. Read generated workcopy from cache ├─────────────────────────────────────► ┌──────────────┐ │ │ SAN (cache) │ │◄───────────────────────────────────────┤ │ │ Binary data (workcopy PNG/JPG) └──────────────┘ │ 5. PUT DynamoDB (MigrateState started=true) ├─────────────────────────────────────► ┌──────────────┐ │ │ DynamoDB │ │◄───────────────────────────────────────┤ MigrateState│ │ 200 OK └──────────────┘ │ 6. PUT S3 Results (workcopy, variations, hash, ...) ├─────────────────────────────────────► ┌──────────────┐ │ Multipart upload (chunked) │ S3 Results │ │◄───────────────────────────────────────┤ Bucket │ │ 200 OK + ETag └──────────────┘ │ 7. Si vidéo: GET Azure (HLS, subtitles) ├─────────────────────────────────────► ┌──────────────┐ │ │ Azure Blob │ │◄───────────────────────────────────────┤ Storage │ │ Binary data (m3u8, ts, vtt) │ (OPV2) │ │ └──────────────┘ │ └─> Puis PUT vers S3 Results │ 8. PUT S3 Sources (fichier source original) ├────────────────────────────────────► ┌──────────────┐ │ Multipart upload │ S3 Sources │ │◄──────────────────────────────────────┤ Bucket │ │ 200 OK + ETag └──────────────┘ │ 9. PUT DynamoDB (MigrateState done=true, steps_done) ├────────────────────────────────────► ┌──────────────┐ │ │ DynamoDB │ │◄──────────────────────────────────────┤ MigrateState│ │ 200 OK └──────────────┘ │ 10. Pendant ce temps, hooks BDD actifs: │ INSERT/UPDATE/DELETE → PUT DynamoDB MigrateLog └────────────────────────────────────────────────────────────────── Note: Étapes 1-9 répétées en parallèle pour N threads (config.nbrThread)

Phase 2 : Traitement insertions

┌─────────────┐ │ Tomcat │ │ (Main Loop) │ └──────┬──────┘ │ 1. SCAN DynamoDB (Action=INSERTED) ├─────────────────────────────────────► ┌──────────────┐ │ │ DynamoDB │ │◄───────────────────────────────────────┤ MigrateLog │ │ List<MigrateLog> (paginated) └──────────────┘ │ 2. Pour chaque log → Thread parallèle: │ a. DELETE DynamoDB (MigrateState) │ ├──────────────────────────────────► ┌──────────────┐ │ │ │ DynamoDB │ │ │◄────────────────────────────────────┤ MigrateState│ │ │ 200 OK └──────────────┘ │ │ │ b. SELECT instance by ID │ ├──────────────────────────────────► ┌──────────────┐ │ │ │ MySQL/ │ │ │◄────────────────────────────────────┤ PostgreSQL │ │ │ ResultSet (1 row) └──────────────┘ │ │ │ c. Rejouer migrateObjectField() │ │ (identique à Phase 1 : SAN + Imaging + S3) │ │ │ d. DELETE DynamoDB (MigrateLog) │ └──────────────────────────────────► ┌──────────────┐ │ │ DynamoDB │ │ ◄─────────────────────────────────────┤ MigrateLog │ │ 200 OK └──────────────┘ │ 3. Si nbr_done > 0 : RETRY (boucle) │ Sinon : FIN Phase 2 └──────────────────────────────────────────────────────────────────

Phase 3 : Bascule (maintenance)

┌─────────────┐ │ Tomcat │ │ (Single │ │ Thread) │ └──────┬──────┘ │ 1. Broadcast Cluster (ClusterEnableMaintenance) ├────────────────────────────────────► ┌──────────────┐ │ │ Autres │ │◄──────────────────────────────────────┤ nœuds │ │ ACK │ Tomcat │ │ └──────────────┘ │ 2. Rejouer Phase 2 (résidus) │ (voir diagramme Phase 2) │ 3. BEGIN TRANSACTION │ ┌────────────────────────────────► ┌──────────────┐ │ │ ALTER TABLE ... MODIFY COLUMN │ DB │ │ └◄──────────────────────────────────┤ │ │ 200 OK └──────────────┘ │ 4. Broadcast Cluster (ClusterDisableMigrationLog) ├─────────────────────────────────────► ┌──────────────┐ │ │ Autres │ │◄───────────────────────────────────────┤ nœuds │ │ ACK └──────────────┘ │ 5. Shutdown Application │ └─> Fermeture connexions (BDD, S3, DynamoDB, HTTP pools) │ 6. Startup Application │ └─> Rechargement structures (type blob maintenant) └──────────────────────────────────────────────────────────────────

Phase 4 : Nettoyage suppressions

┌─────────────┐ │ Tomcat │ │ (Main Loop) │ └──────┬──────┘ │ 1. SCAN DynamoDB (Action=DELETED) ├────────────────────────────────────► ┌──────────────┐ │ │ DynamoDB │ │◄──────────────────────────────────────┤ MigrateLog │ │ List<MigrateLog> (DELETED entries) └──────────────┘ │ 2. Pour chaque log: │ a. SELECT instance (vérif sécurité) │ ├──────────────────────────────────► ┌──────────────┐ │ │ │ MySQL/ │ │ │◄────────────────────────────────────┤ PostgreSQL │ │ │ ResultSet (peut être vide) └──────────────┘ │ │ │ b. Si vraiment à supprimer: │ │ DELETE S3 (blobPath) │ ├──────────────────────────────────► ┌──────────────┐ │ │ │ S3 Sources │ │ │◄────────────────────────────────────┤ Bucket │ │ │ 204 No Content └──────────────┘ │ │ │ c. DELETE DynamoDB (MigrateLog) │ └──────────────────────────────────► ┌──────────────┐ │ │ DynamoDB │ │ ◄─────────────────────────────────────┤ MigrateLog │ │ 200 OK └──────────────┘ │ 3. Si nbr_done > 0 : RETRY (boucle) │ Sinon : FIN Phase 4 └──────────────────────────────────────────────────────────────────

Phase 5 : Nettoyage local (SAN)

┌─────────────┐ │ Tomcat │ │ (Thread N) │ └──────┬──────┘ │ 1. SELECT instances WHERE blob IS NOT NULL ├────────────────────────────────────► ┌──────────────┐ │ │ MySQL/ │ │◄──────────────────────────────────────┤ PostgreSQL │ │ ResultSet (instances avec blob) └──────────────┘ │ 2. Pour chaque instance (parallèle): │ Check existence + DELETE file │ └──────────────────────────────────► ┌──────────────┐ │ │ SAN (local) │ │ File.delete() │ │ │ └──────────────┘ │ Aucun échange réseau externe (local filesystem only) └──────────────────────────────────────────────────────────────────

Protocoles et APIs utilisés

1. Base de données SQL

  • Opérations :

    • Lecture structures, instances

    • Modification structure (Phase 3)

2. AWS S3

  • Opérations 

    • Upload fichiers (sources, results)

    • Lecture des fichiers existants

    • Suppression fichiers obsolètes

3. AWS DynamoDB

  • Opérations :

    • Écriture MigrateLog, MigrateState

    • Lecture MigrateState

    • Suppression après traitement

    • Parcours complet (avec FilterExpression)

4. Imaging Server

  • Opérations :

    • Génération thumbnail

    • Exécution preset

5. Azure Blob Storage (OPV2)

  • APIs :

    • Liste fichiers HLS/subtitles

    • Téléchargement local temporaire

  • Utilisation : Lecture seule (migration depuis legacy)

Bande passante et volumétrie

Points de contention réseau

  1. Upload S3 (Phase 1)

    • Goulot principal :

      • Bande passante sortante serveur Tomcat

      • Lecture des fichiers dans la SAN (iops)

  2. Imaging Server (Phase 1)

    • Goulot :

      • CPU Imaging Server (génération thumbnails)

      • Lecture des fichiers dans la SAN (iops)

  3. DynamoDB (Phases 2, 4)

    • Goulot : Bande passante sortante serveur Tomcat

  4. Base SQL (Phase 3)

    • Goulot : Lock tables pendant ALTER TABLE

    • Durée : Quelques secondes à minutes (dépend taille table)

    • Impact : Site indisponible pendant ce temps


Les 5 phases de migration

Phase 1 : Copie initiale (phaseOne)

Objectif

Copier l'état actuel de tous les assets de MC1 vers MC2 sans interruption de service.

Flux détaillé

┌─────────────────────────────────────────────────────────────────────────────┐ │ PHASE 1 : COPIE INITIALE │ └─────────────────────────────────────────────────────────────────────────────┘ 1. Configuration initiale └─> Activer MigrateLog (DynamoDB + Cluster) └─> Créer ThreadPool (nbrThread workers) 2. Pour chaque ObjectType (wxm_dam_asset, wxm_dam_document, ...) ├─> Identifier les champs file/image ├─> Compter les instances (estimation) └─> Pour chaque instance ayant un fichier └─> [PARALLÈLE] Soumettre au ThreadPool ├─> Lire état MigrateState (DynamoDB) │ └─> Si done=true && !forceCheck: SKIP ├─> Marquer started=true (DynamoDB) ├─> Lire fichier source (SAN) │ └─> Binary.getServerPath() ├─> [ÉTAPE] Gestion MC1.5 (si applicable) │ ├─> Tester existence S3: s3://sources/{mc15_path} │ └─> Si trouvé: │ ├─> Copier S3→S3 (sources + results) │ ├─> Marquer pour suppression (MigrateLog.DELETED) │ └─> TERMINÉ (c'est un MC1.5) ├─> [ÉTAPE] copyWorkcopy │ ├─> Lire cache SAN ou générer (ImagingServer) │ ├─> Générer 10000×10000 (PNG/JPG selon source) │ ├─> Upload S3 Results: {blobPath}.d/thumbnail_10000x10000.{format} │ └─> Marquer étape done (DynamoDB) ├─> [ÉTAPE] copyWorkcopyAnimated │ ├─> Chercher cache animated WebP/PNG │ ├─> Générer 10000×10000 animé (ImagingServer) │ ├─> Upload S3 Results: {blobPath}.d/animated_10000x10000.webp │ └─> Marquer étape done ├─> [ÉTAPE SI VIDEO/AUDIO] copyVideo │ ├─> Charger variations depuis VariationsProvider │ ├─> Pour chaque résolution (FHD, HD, MD, SD) │ │ ├─> Extraire variation en fichier temp │ │ └─> Upload S3 Results: {blobPath}.d/mediaconvert/files/output_{res}.mp4 │ ├─> Générer manifest JSON │ ├─> Upload S3 Results: {blobPath}.d/mediaconvert/job/output.json │ └─> Marquer étape done ├─> [ÉTAPE SI VIDEO] copyVideoHls │ ├─> Requête OPV2Database.getMediaFilesHLS() │ ├─> Connexion Azure Blob Storage │ ├─> Pour chaque fichier .m3u8/.ts │ │ ├─> Download depuis Azure │ │ └─> Upload S3 Results: {blobPath}.d/mediaconvert/hls/{file} │ └─> Marquer étape done ├─> [ÉTAPE SI VIDEO] copyVideoSubtitle │ ├─> Requête OPV2Database.getMediaFilesSubtitles() │ ├─> Connexion Azure Blob Storage │ ├─> Pour chaque sous-titre │ │ ├─> Download .vtt depuis Azure │ │ └─> Upload S3 Results: {blobPath}.d/subtitles/sources/{lang}.vtt │ ├─> Générer subtitles.json │ ├─> Upload S3 Results: {blobPath}.d/subtitles/sources/subtitles.json │ └─> Marquer étape done ├─> [ÉTAPE] copyVariations │ ├─> Pour prefix (Thumbnail, Animated) │ │ └─> Pour dimension (Tiny=100, Small=200, Big=600, Screen=2000) │ │ └─> Pour format (png, jpg, webp, gif) │ │ ├─> Chercher cache SAN │ │ ├─> Si trouvé: │ │ │ ├─> Upload S3 Results: {preset_name}/{variation} │ │ │ └─> Appel MC2 API: savePresetOutputPath() │ │ └─> Sinon: SKIP │ │ │ └─> Pour preset custom (circle_png, square_2000, square_png) │ ├─> Vérifier preset existe (PresetManager) │ ├─> Si valide: │ │ ├─> Exécuter preset (ImagingServer) │ │ ├─> Upload S3 Results │ │ └─> Appel MC2 API: savePresetOutputPath() │ └─> Sinon: WARNING log ├─> [ÉTAPE SI PDF/OFFICE] copyPdf │ ├─> Charger variation "pdf" │ ├─> Extraire en fichier temp │ ├─> Upload S3 Results: {blobPath}.d/pdf.d/base.pdf │ └─> Marquer étape done ├─> [ÉTAPE SI PDF/OFFICE] copyExtractedText │ ├─> ImagingFactory.getImaging().getExtractedText() │ ├─> Lire texte en UTF-8 │ ├─> Upload S3 Results: {blobPath}.d/extract.text.d/base.txt │ └─> Marquer étape done ├─> [ÉTAPE] copyHashImage │ ├─> Chercher cache hashimage.png │ ├─> Si trouvé: │ │ └─> Upload S3 Results: {blobPath}.d/hashimage.d/hashimage.png │ └─> Marquer étape done ├─> [ÉTAPE SI doCopySource] copySource │ ├─> Tester existence S3 Sources: {blobPath} │ ├─> Si absent OU taille différente: │ │ ├─> Copier fichier source en temp │ │ ├─> Upload S3 Sources: {blobPath} │ │ └─> Si manualIngestion: créer IngestionConfiguration.json │ └─> Marquer étape done └─> Marquer done=true (DynamoDB) 3. Pendant toute la phase 1 └─> Hooks actifs sur BDD: ├─> INSERT → MigrateLog.INSERTED (DynamoDB) ├─> UPDATE → MigrateLog.DELETED (ancien) + INSERTED (nouveau) └─> DELETE → MigrateLog.DELETED (DynamoDB)

Points de reprise

  • Chaque étape est atomique et marquée dans DynamoDB

  • En cas d'arrêt : reprend au dernier état enregistré

  • Option forceCheck : refait les étapes manquantes

  • Option forceRedo : ignore l'état et refait tout


Phase 2 : Traitement des insertions (phaseTwo)

Objectif

Traiter les fichiers créés ou modifiés pendant la Phase 1 grâce au journal d'événements.

Flux détaillé

┌─────────────────────────────────────────────────────────────────────────────┐ │ PHASE 2 : TRAITEMENT DES INSERTIONS │ └─────────────────────────────────────────────────────────────────────────────┘ BOUCLE WHILE (tant qu'il reste des entrées): 1. Thread d'estimation (arrière-plan) ├─> Scan DynamoDB MigrateLog ├─> Filtre: Action=INSERTED ├─> Compte total → Logger.phaseTwoNbrTodo() └─> Continue en parallèle pendant 5 secondes 2. Thread principal (traitement) └─> Scan DynamoDB MigrateLog (Action=INSERTED) └─> Pour chaque entrée MigrateLog ├─> Vérifier ObjectType dans liste migration │ └─> Si hors scope: SKIP ├─> [PARALLÈLE] Soumettre au ThreadPool │ │ │ ├─> Supprimer MigrateState pour cette propriété │ │ └─> DELETE DynamoDB MigrateState │ │ (force retraitement car fichier a changé) │ │ │ ├─> Charger instance depuis BDD │ │ └─> CTObjects.loadObjectById() │ │ │ ├─> Si instance existe: │ │ ├─> Récupérer IObjectField │ │ └─> Appeler migrateObjectField() │ │ (identique à Phase 1 - toutes les étapes) │ │ │ ├─> Logger.phaseTwoNbrDone++ │ │ │ └─> Supprimer entrée MigrateLog │ └─> DELETE DynamoDB MigrateLog │ (traité, on nettoie) └─> Logger progression 3. Synchronisation └─> Attendre tous les ThreadPool tasks └─> Compter nbr_done 4. Condition de sortie └─> SI nbr_done == 0: FIN (plus rien à traiter) └─> SINON: RETRY (boucle suivante)

Cas d'usage typiques

  • Création asset : Utilisateur upload un nouveau fichier → INSERTED

  • Modification asset : Utilisateur remplace un fichier → DELETED + INSERTED

  • Suppression asset : Utilisateur supprime un asset → DELETED (traité en Phase 4)

Gestion des conflits

  • La suppression du MigrateState force le retraitement complet

  • Si l'instance n'existe plus en BDD : log DELETED sera traité en Phase 4

  • Évite les incohérences entre cache DynamoDB et état réel


Phase 3 : Bascule ⚠️ (phaseThree)

Objectif

Point de non-retour : Basculer définitivement vers MC2 en modifiant la structure de la base de données.

Flux détaillé

┌─────────────────────────────────────────────────────────────────────────────┐ │ PHASE 3 : BASCULE (POINT DE NON-RETOUR) │ └─────────────────────────────────────────────────────────────────────────────┘ 1. Activation mode maintenance ├─> MigrateService.enableMaintenance() ├─> Bloque les opérations CREATE/UPDATE/DELETE └─> Broadcast Cluster: ClusterEnableMaintenance (tous les serveurs passent en maintenance) 2. Traitement des derniers résidus └─> Appel phaseTwo() (traiter tout ce qui a été créé entre-temps) 3. Désactivation ElasticSearch └─> ServiceManager.requestService(ElasticSearchService) └─> service.disable() (évite l'indexation pendant modification structure) 4. Modification structure BDD ├─> Connexion BDD: CTDatabaseFactory.getOrCreateDefaultDriver() └─> Pour chaque ObjectType à migrer ├─> Charger structure: CTObjectStructure.load(objectname) └─> Pour chaque champ file/image ├─> Récupérer CTObjectFieldWritable ├─> Modifier type: field.setType("blob") ├─> Sauvegarder structure │ └─> structure.Update(driver, null, false, true) └─> Logger.phaseThreeObjectnameDone() 5. Désactivation logs migration └─> MigrateService.disableMigrationLog() └─> Broadcast Cluster: ClusterDisableMigrationLog (plus d'enregistrement des modifications) 6. Redémarrage moteur ├─> Startup.stop() │ └─> Fermeture connexions, threadpools, services └─> Startup.start() └─> Réinitialisation complète ├─> Recharge structures (maintenant en blob) ├─> Réactive services (ElasticSearch, etc.) └─> Recharge configuration MC2

⚠️ Avertissements critiques

  1. Interruption de service

    • Le site est en maintenance (utilisateurs bloqués)

    • Durée : Variable selon nombre d'ObjectTypes (quelques secondes à minutes)

  2. Point de non-retour

    • Une fois les champs convertis en blob, retour en arrière nécessite :

      • Backup BDD à restaurer

  3. Pré-requis absolus

    • Phase 1 terminée à 100%

    • Phase 2 stable (plus d'entrées ou très peu)

    • Backup BDD à jour

  4. Risques

    • Échec redémarrage moteur → site indisponible

    • Cluster non synchronisé → nœuds en état différent


Phase 4 : Nettoyage des suppressions (phaseFour)

Objectif

Supprimer de S3 les fichiers obsolètes marqués DELETED dans le journal.

Flux détaillé

┌─────────────────────────────────────────────────────────────────────────────┐ │ PHASE 4 : NETTOYAGE DES SUPPRESSIONS │ └─────────────────────────────────────────────────────────────────────────────┘ BOUCLE WHILE (tant qu'il reste des entrées): 1. Thread d'estimation (arrière-plan) ├─> Scan DynamoDB MigrateLog ├─> Filtre: Action=DELETED ├─> Compte total → Logger.phaseFourNbrTodo() └─> Continue en parallèle 2. Thread principal (traitement) └─> Scan DynamoDB MigrateLog (Action=DELETED) └─> Pour chaque entrée MigrateLog ├─> Extraire PropertyValue (blobPath à supprimer) ├─> Vérification sécurité (fichier vraiment inutilisé ?) │ ├─> Charger instance depuis BDD │ │ └─> CTObjects.loadObjectById() │ │ │ ├─> Si instance existe: │ │ ├─> Lire valeur actuelle du champ │ │ └─> Si valeur == PropertyValue: │ │ ├─> WARNING: "Fichier marqué à supprimer mais encore utilisé !" │ │ └─> remove = false (ne pas supprimer) │ │ │ └─> Sinon: remove = true ├─> Si remove == true: │ └─> Supprimer de S3 Sources │ └─> awsService.getSourcesStorage().delete(PropertyValue) ├─> Logger.phaseFourNbrDone++ └─> Supprimer entrée MigrateLog └─> DELETE DynamoDB MigrateLog 3. Synchronisation └─> Attendre traitement complet 4. Condition de sortie └─> SI nbr_done == 0: FIN └─> SINON: RETRY

Cas traités

  • Fichiers MC1.5 : Anciens chemins S3 migrés depuis MC1.5

  • Fichiers remplacés : Assets dont le fichier a changé en Phase 1/2

  • Assets supprimés : Instances supprimées pendant la migration

Sécurité

  • Vérification double : le fichier est-il vraiment inutilisé ?

  • Log WARNING si détection usage inattendu

  • Pas de suppression en mode simulate


Phase 5 : Nettoyage local (phaseFive)

Objectif

Supprimer les fichiers locaux du SAN maintenant que tout est en S3.

Flux détaillé

┌─────────────────────────────────────────────────────────────────────────────┐ │ PHASE 5 : NETTOYAGE LOCAL (SAN) │ └─────────────────────────────────────────────────────────────────────────────┘ 1. Identification des ObjectTypes └─> _findObjectnamesToRemovePj() ├─> Pour chaque ObjectType │ └─> Identifier champs type "blob" │ (conversion faite en Phase 3) └─> Retourne Set<ObjectType> 2. Estimation └─> Pour chaque ObjectType ├─> Compter instances avec blob non vide └─> Logger.removePjAddObjectsTodo() 3. Traitement parallèle └─> Pour chaque ObjectType └─> Pour chaque instance avec blob ├─> [PARALLÈLE] Soumettre au ThreadPool │ │ │ └─> Pour chaque champ blob │ │ │ ├─> Extraire PropertyValue (blobPath) │ │ │ ├─> Construire chemin local SAN │ │ └─> file/{objecttype}/{blobPath} │ │ Exemple: file/wxm_dam_asset/assets/2024/asset123.jpg │ │ │ ├─> Vérifier existence fichier local │ │ └─> Startup.getSanPathOf(filePath).toFile() │ │ │ ├─> Si fichier existe: │ │ ├─> Logger: "Suppression {size} bytes" │ │ └─> CTFile.delete(file) │ │ (suppression physique SAN) │ │ │ └─> Logger.removePjObjectFieldDone++ └─> Attendre ThreadPool tasks 4. Résultat └─> Espace disque SAN libéré └─> Fichiers uniquement en S3

Conditions de suppression

  • Le champ est de type blob (Phase 3 effectuée)

  • Le fichier existe physiquement en local

  • Pas de vérification S3 (on fait confiance à Phase 1)

Espace libéré

  • Fichiers sources (file/)

  • Pas de suppression du cache (géré séparément par CacheCleaner)


Configuration et paramètres

MigrateConfig

MigrateController.MigrateConfig config = new MigrateConfig(); // Mode simulation (aucun changement persisté) config.simulate = false; // false = vraie migration // Copie des sources config.doCopySource = true; // true = upload sources vers S3 // Stratégies de reprise config.forceCheck = false; // true = refaire étapes non complétées config.forceRedo = false; // true = tout refaire depuis zéro // Parallélisation config.nbrThread = 4; // Nombre de workers (0 = auto: CPU/2) // Redémarrage automatique config.enableRestart = true; // true = redémarre après Phase 3 // false = s'arrête et attend relance manuelle


Monitoring et observabilité

Statistiques temps réel

L'écran administratif permet de suivre l'évolution.
Il présente également des estimations de temps par phase.

image-20260430-074516.png

Logs (Log4j)

# Configuration logger Nom: migration.log

Remarques

Le principe de la migration est de ne tolérer aucune erreur.
Donc à la moindre erreur, la procédure s’arrête en indiquant dans l'écran administratif l’erreur.
Cette dernière est aussi logguée dans le log.

Il faudra résoudre l’erreur puis relancer la migration.

Les erreurs classiques:

  • un bug dans l’EVP fait qu’il existe plusieurs points d’entrée pour les HLS: la procédure ne peut pas savoir lequel est le bon. Il faudra interroger la base de données et marquer comme supprimés les points d’entrée obsolète.

  • un propriété file est renseignée dans la base de donnée, mais on ne trouve pas le fichier dans la SAN. Il faudra soit ajouter le fichier dans la SAN, soit vider la propriété concernée.


Glossaire

Terme

Description

Terme

Description

MC1

MediaCloud 1 - Architecture historique avec stockage local (SAN)

MC2

MediaCloud 2 - Architecture cloud avec S3 et traitement asynchrone

MC1.5

État intermédiaire avec sources déjà en S3 mais structure BDD en file

BlobPath

Chemin du fichier dans S3 Sources (ex: assets/2024/file.jpg)

Workcopy

Version haute résolution (10000×10000) pour traitement

Variation

Version transformée (thumbnail, preset) d'un asset

Preset

Transformation nommée (ex: circle_pngsquare_2000)

Ingestion

Processus MC2 d'analyse et génération variations asynchrone

SAN

Storage Area Network - Stockage local haute performance

OPV2

Online Video Platform v2 - Système de gestion vidéos (Azure)

HLS

HTTP Live Streaming - Format vidéo adaptatif