ajoute le cache Redis sur les lectures lourdes (bloc B)

Catalogue (labs, banque de quiz officielle, cours) et liste des
classes du tableau de bord prof - jamais d'expiration, invalidation
explicite par les commandes de synchronisation et par
archiver/désarchiver/purge RGPD.

Bug trouvé en vérifiant en réel sur le serveur (pas en local, où
CACHE_STORE=array ne sérialise jamais rien) : mettre en cache des
modèles Eloquent hydratés plante à la relecture via Redis
("incomplete class" à l'unserialize). Corrigé en cachant les attributs
bruts et en reconstruisant de vrais modèles avec Model::hydrate().

Mesuré en réel : 5 appels directs = 5 requêtes SQL, 5 appels via le
cache = 1 requête SQL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-10-14 07:34:55 +02:00
parent ecf3dfb21d
commit ca544ac620
15 changed files with 250 additions and 10 deletions

View file

@ -1098,3 +1098,30 @@ Le choix qui limite le plus le risque ici : exposer seulement ce qui est déjà
**Suite**
Prochain chantier du bloc B : cache Redis sur les lectures lourdes, avec mesure avant/après dans Grafana.
## 2026-10-14 — Cache Redis sur les lectures lourdes (bloc B)
Deuxième chantier du bloc B : cache sur le catalogue (labs, banque de quiz officielle, cours) et sur la liste des classes du tableau de bord prof — relue à chaque chargement de page et, pendant une séance en cours, toutes les 15 secondes par le rafraîchissement automatique.
**Fait**
- `App\Services\Catalogue\CacheCatalogue` (labs, quiz officiels, cours) et `App\Services\CacheTableauDeBordProf` (classes d'un prof, avec le nombre d'élèves) : jamais d'expiration, invalidation explicite au bon moment plutôt qu'une durée de vie devinée.
- Catalogue : invalidé par les trois commandes de synchronisation (`labs:synchroniser`, `quiz:synchroniser-banque`, `cours:synchroniser`), déjà rejouées à chaque déploiement.
- Tableau de bord prof : invalidé à l'archivage, au désarchivage, et par la purge RGPD (`classes:purger-archivees`).
- Branché sur l'API publique (`/api/v1/labs`, `/quiz`, `/cours`), `/bibliotheque-app`, et le cockpit d'une classe (`/mes-classes/{classe}`, formulaire de séance).
**Bug trouvé en vérifiant en réel, pas en local** : mettre en cache directement des modèles Eloquent hydratés plante à la relecture avec « incomplete class » — `unserialize()` reconstruit un objet PHP, et selon le moment où il est relu, l'autoloader n'a pas forcément encore chargé sa classe. Les tests locaux ne l'ont pas vu : `CACHE_STORE=array` en test ne sérialise jamais rien, contrairement à Redis en production. Corrigé en mettant en cache les attributs bruts (`getAttributes()`, un tableau de valeurs scalaires, aucun risque de ce genre) et en reconstruisant de vrais modèles à la lecture avec `Model::hydrate()` — aucun changement nécessaire côté vues ou ressources API, qui continuent de recevoir de vraies collections Eloquent.
**Mesuré sur le serveur réel** (avant/après, par `DB::listen()` — l'équivalent de ce que Grafana afficherait via les métriques MariaDB déjà exposées, juste lu directement plutôt que dans l'interface, faute d'extension Chrome connectée cette fois) :
- `Lab::orderBy(...)->get()` direct, 5 appels : **5 requêtes SQL**.
- `CacheCatalogue::labs()`, 5 appels : **1 requête SQL** (le premier appel, un cache miss ; les 4 suivants sont servis par Redis). Même résultat pour `quizOfficiels()` et `cours()`, et pour `CacheTableauDeBordProf::classes()`.
- Invalidation vérifiée : une valeur sentinelle posée sur chaque clé disparaît bien après avoir rejoué la commande de synchronisation correspondante.
- Vérification HTTP complète (prof jetable, supprimé ensuite) : `/mes-classes`, le formulaire de séance de `/mes-classes/{classe}` (liste des labs), `/bibliotheque-app` et `/api/v1/labs` affichent tous les bonnes données, réellement mises en cache.
**Comprendre**
La leçon : un test local qui passe ne prouve rien sur un comportement qui dépend du driver de cache réellement utilisé. `CACHE_STORE=array` en test est rapide et pratique, mais invisible aux bugs de sérialisation — seule une vérification contre le vrai Redis de production les révèle. Pour ce genre de risque (sérialisation, comportement spécifique à un driver), le test local ne remplace pas la vérification en réel : il la complète.
**Suite**
Prochain chantier du bloc B : optimisation MariaDB (index, `EXPLAIN`, requêtes N+1, jeu de données de charge).