Aller au contenu

Purge du contenu des classes virtuelles

Version métier : voir Purge du contenu des classes virtuelles.

Une classe virtuelle accumule du contenu avant même d’avoir commencé : messages postés dans le chat de la session, fichiers partagés par le formateur, blobs du test de bande passante, enregistrements d’une éventuelle répétition en salle de test. Rien ne nettoyait cela jusqu’ici — le serveur WebSocket du LMS ne vide que son cache mémoire, les objets MinIO et les RoomMessage restaient.

La commande balaie chaque nuit les classes virtuelles du jour et publie, pour chacune, un message Messenger différé qui se déclenche exactement 2 h avant lmsStartDate. À la consommation, le message supprime les messages du chat et tous les fichiers de la classe.

Le repère des 2 h n’est pas arbitraire : c’est l’instant précis où la vraie salle LiveKit s’ouvre et où la salle de répétition se ferme (VirtualClassRoomResolver::resolve(), realOpensAt = lmsStartDate -2h). La classe démarre donc toujours sur une salle propre.

Planification GitLab :

  • Description : Purge virtual class content
  • ID du schedule : à renseigner après création
  • Cron : 0 1 * * * (fuseau : Europe/Paris) — 01:00 Paris chaque jour.
  • Branche : refs/heads/master
  • État : à créer
  • Variable injectée : SCHEDULE_TYPE=purge_virtual_class_content

TODO(review) : créer le schedule dans GitLab > Build > Pipeline schedules, puis reporter son ID ici et dans le tableau de la page pipeline.

Job CI : purge_virtual_class_content dans ci-jobs/.scheduled-ci.yml (stage purge_virtual_class_content). Le job se connecte en SSH à $DATABASE_SERVER_URL et exécute la commande dans le conteneur applicatif $APP_NAME.

Commande exécutée :

Fenêtre de terminal
docker exec $APP_NAME php bin/console scheduled:purge-virtual-class-content

Option --date=Y-m-d disponible pour rejouer manuellement un jour donné (défaut : aujourd’hui).

  1. VirtualClassRepository::findHostingByDay() récupère les classes virtuelles dont sessionDate.lmsStartDate tombe dans la journée demandée, fenêtre calculée en Europe/Paris puis convertie en UTC via getDateRange().
    • Filtre vc.hosting = true : les sous-CVS partagent la salle et les préfixes de fichiers de leur CVS principale, les purger reviendrait à supprimer deux fois le même contenu.
    • Filtre sd.internalId != 100 : les sessions d’administration sont exclues, comme partout ailleurs dans ce repository.
  2. Aucune classe ce jour-là → warning et sortie en SUCCESS.
  3. Pour chaque classe : purgeAt = lmsStartDate - 2 heures, puis delay = max(0, (purgeAt - maintenant) * 1000) millisecondes. Le max(0, …) couvre les classes démarrant avant 03:00, dont le -2 h est déjà passé quand le cron tourne à 01:00 : le message est alors consommé immédiatement.
  4. Publication d’un PurgeVirtualClassContentMessage(virtualClassId) sur MessageBusInterface avec un DelayStamp, comme le fait ReminderService::executeVirtualClassReminder(). Le délai reste borné à moins de 24 h, aucun message ne campe des jours dans RabbitMQ.
  5. PurgeVirtualClassContentMessageHandler consomme le message :
    • classe virtuelle introuvable (supprimée entre la planification et la consommation) → sortie silencieuse, sans erreur ;
    • suppression des objets MinIO d’abord — virtual-class/{id}/ et test/uploads/{id}/ sur erevo-cdn, virtual-class/{id}/ sur erevo-lms (qui contient les enregistrements). Un échec en cours de route ne doit pas laisser des fichiers orphelins dont les messages porteurs d’URL auraient déjà disparu ;
    • suppression des ReportMessage de la salle avant les RoomMessage : ReportMessage.messageReported est une clé étrangère non nullable sans onDelete, une suppression naïve part en violation de contrainte. Le replyTo auto-référent de RoomMessage est en onDelete: SET NULL côté base, il ne demande aucun passage supplémentaire ;
    • la salle (Room) elle-même est conservée, seul son contenu est vidé.

Les deux DELETE DQL lient l’identifiant de salle avec le type UuidType explicite. Sans ce type, Doctrine envoie l’UUID en chaîne, la colonne binaire ne correspond à rien et la suppression ne fait rien — silencieusement.

  • VirtualClass — lecture (classes du jour, statut hosting).
  • SessionDate — lecture (lmsStartDate, internalId), chemin vers la salle.
  • Room — lecture, conservée.
  • RoomMessage — suppression de tous les messages de la salle.
  • ReportMessage — suppression des signalements portant sur ces messages.
  • Le handler capture toute Throwable, la remonte dans Sentry via AppHelper::sentryCatchExceptionWithContext() avec le contexte purgeVirtualClassContent (virtualClassId), puis la relance. Le transport retente 3 fois (retry_strategy.max_retries: 3) et gare le message dans le transport failed (doctrine://default?queue_name=failed) en cas d’échec persistant. Une classe non purgée reste donc visible, elle n’échoue pas en silence.
  • Côté commande, aucune exception dédiée : une erreur de dispatch remonte telle quelle, le job GitLab échoue et la journée sera rattrapée manuellement avec --date.
  • RabbitMQ — transport purge-virtual-class-content (exchange fanout purge_virtual_class_content), consommé par le worker supervisor purge-virtual-class-content.
  • MinIO — bucket erevo-cdn (fichiers partagés et blobs de test) et bucket erevo-lms (enregistrements LiveKit), exposés via les storages Flysystem cdn.storage et lms.storage.
  • MariaDB — suppression des RoomMessage et ReportMessage.

Les collections MongoDB du LMS ne sont pas nettoyées : virtual-class-state.sharedFiles et virtual-class-recordings conservent leurs entrées alors que les objets MinIO correspondants ont disparu. Le LMS peut donc encore afficher des fichiers ou des enregistrements dont le lien est mort. internal-api n’a pas d’accès Mongo et le périmètre retenu exclut toute modification du dépôt LMS.

TODO(review) : prévoir côté LMS le nettoyage de virtual-class-state.sharedFiles et virtual-class-recordings, ou exposer un endpoint que ce handler appellerait.

  • Logs : GitLab > Build > Pipelines > purge_virtual_class_content (sortie SSH : nombre de classes, heure de purge et délai par classe).
  • Logs du worker : docker logs $APP_NAME → programme supervisor purge-virtual-class-content.
  • Profondeur de file surveillée par liip_monitor (messenger.transport.purge-virtual-class-content, seuils 10 / 20) et visible sur /healthcheck.
  • Alerting : Sentry capture les exceptions du handler, contexte purgeVirtualClassContent.
  • Commande : src/Command/Scheduled/PurgeVirtualClassContentCommand.php
  • Job CI : ci-jobs/.scheduled-ci.yml (job purge_virtual_class_content), stage déclaré dans .gitlab-ci.yml
  • Message / handler : App\Message\PurgeVirtualClassContentMessage, App\MessageHandler\PurgeVirtualClassContentMessageHandler
  • Repository : App\Repository\VirtualClassRepository::findHostingByDay()
  • Configuration : config/packages/messenger.yaml, config/packages/flysystem.yaml, config/packages/liip_monitor.yaml, docker/supervisor/messenger.conf, docker/supervisor/messenger-local.conf
  • Tests : tests/Command/CommandsTests.php