# ADR 0003 — Stock chaud dans Redis, Heat pour le ménage, files gérées par Horizon - **Date** : 2026-10-08 - **Statut** : accepté ## Contexte Étape 11 : un lab pris dans le stock doit s'ouvrir en moins de 5 s, alors que Heat met 20 à 40 s à construire une stack. Il faut donc des stacks construites d'avance, données à un élève au moment où le prof l'admet, sans jamais donner la même stack à deux élèves, ni perdre une stack (une VM qui tourne pour rien coûte de la RAM, rare sur ce serveur de 32 Go). ## Décisions 1. **Une liste Redis par lab** (`stock:pretes:{lab}`) contient les stacks prêtes. La prise est un script Lua (`LPOP` + `ZADD stock:attribuees`) exécuté d'un bloc par Redis : une stack est toujours soit dans la liste, soit marquée « en cours d'attribution », jamais nulle part. 2. **Redis fait foi pour ce qui est prêt ; Heat sert au ménage.** Chaque minute, `labs:remplir-stock` liste les stacks `stock-…` dans Heat et supprime celles que plus rien ne référence (ni la liste, ni une attribution, ni une session en base, ni une construction en cours), après 10 min de marge. Une stack orpheline n'est jamais « récupérée » dans le stock : la supprimer est plus sûr que risquer de la donner deux fois. 3. **Cible du stock** = taille par défaut (1 stack par lab) + élèves des séances qui commencent dans les 10 min ou déjà ouvertes et pas encore servis, dans la limite de `capacite_vm` (15 VM, voir journal de l'étape 11). 4. **Fiabilité commune** (`FabriqueStack`) pour le stock et les sessions : 3 essais, chaque essai porte son nom (`-e1`, `-e2`, `-e3`) et supprime sa stack ratée ; avant un essai, les restes des précédents sont supprimés. Disjoncteur sur le stock : 3 échecs définitifs de suite sur un lab → plus de commande pour lui pendant 30 min. 5. **Laravel Horizon** remplace le `queue:work` unique de l'étape 10. Deux superviseurs à nombre fixe de processus : `sessions` (4) et `stock` (2). Un élève n'attend jamais derrière le remplissage du stock, et plusieurs stacks se construisent en parallèle (chaque job bloque un processus pendant qu'il attend Heat). Horizon était déjà prévu dans l'architecture du CLAUDE.md ; le besoin réel apparaît ici. Tableau de bord `/horizon` fermé à tous en production. ## Alternatives écartées - **Une table MariaDB pour le stock** : possible, mais la prise atomique demande un verrou de ligne (`SELECT … FOR UPDATE SKIP LOCKED`) ; la liste Redis + Lua est plus simple, et le prompt de déploiement la demande. - **Récupérer les stacks orphelines au lieu de les supprimer** : économise une construction, mais ouvre une fenêtre où la même stack pourrait être dans la liste deux fois. Refusé. - **Plusieurs unités systemd `queue:work`** : faisable, mais sans métriques ni gestion propre de plusieurs files ; Horizon fait les deux. ## Conséquences - Les VM du stock tournent en permanence (≈ 270 Mo réels chacune au repos). - Si Redis est vidé, le stock est perdu mais pas les VM : le ménage les supprime 10 min plus tard, et le stock se reconstruit. - `retry_after` de la file Redis (330 s) doit rester supérieur au timeout des superviseurs Horizon (300 s), lui-même supérieur au timeout des jobs (280 s).