Carte des sept DAGs et de leurs planifications en UTC, lecture du verdict de dérive, réentraînement sans perdre le modèle en service, rétention et reprise après incident. Renvoie aux vues et aux ADR qui portent le détail.
6.1 KiB
70 · Pilotage des traitements automatisés
Ce document dit à l'opérateur quoi surveiller, comment lire ce qu'il voit et quoi faire quand un traitement déraille. Il ne redit pas le fonctionnement : la table des DAGs vit dans 10-infra.md, la chaîne de données dans 40-data.md, la dérive dans l'ADR 0013, la rétention dans l'ADR 0019, la supervision dans 60-observabilite.md.
Carte des traitements
Les planifications sont en UTC : Airflow n'a pas de fuseau configuré. En heure de Paris l'été, ajouter deux heures.
| DAG | Planification (UTC) | Ce qu'il fait | Même traitement à la main |
|---|---|---|---|
mock_api_import |
chaque heure à :45 | importe la mesure de l'heure pile de l'API Mock, une par site | python -m app.etl.mock_api_import --start-time … --end-time … |
ml_score |
chaque heure pile | score le pas horaire suivant, écrit prediction |
make ml-score |
alertes |
chaque heure à :15 | détecte les alertes internes, puis génère les recommandations | make detect-alerts, puis make recommendations |
retention |
03:20 | exporte vers Garage les chunks de reading de plus de 1 095 jours, puis les supprime |
python -m app.etl.reading_retention |
derive |
05:30 | calcule le rapport de dérive du modèle sur 168 h | python -m app.monitoring.drift |
ml_train |
manuel | réentraîne LightGBM et écrase le modèle | make ml-train |
historical_import |
manuel | importe le jeu historique CSV | python -m app.etl.historical_import --csv … --metadata … |
Les modules app.* se lancent depuis apps/backend (uv run python -m …). Tous les DAGs ont
max_active_runs=1 et catchup=False : un retard ne rejoue pas les heures manquées.
Où regarder
Sur la machine, chaque environnement vit dans /srv/enervision/<env> et n'écoute que sur la
boucle locale : on y accède par tunnel SSH.
| Quoi | Production | Recette | Dev |
|---|---|---|---|
| Interface Airflow | 127.0.0.1:8080 |
127.0.0.1:8082 |
127.0.0.1:8084 |
| Grafana (production seulement) | 127.0.0.1:3001 |
- | - |
| Mailpit, où arrivent les alertes | 127.0.0.1:8025 |
127.0.0.1:8026 |
127.0.0.1:8027 |
ssh -L 8080:127.0.0.1:8080 -L 3001:127.0.0.1:3001 -L 8025:127.0.0.1:8025 root@<IP-VM-G3>
En ligne de commande, depuis /srv/enervision/<env> :
compose="docker compose -f docker-compose.yml -f docker-compose.prod.yml"
$compose exec airflow-apiserver airflow dags list-runs derive # derniers passages
$compose exec airflow-apiserver airflow dags trigger ml_train # lancement manuel
$compose logs --tail=100 airflow-scheduler # les tâches tournent ici
Aucune alerte ne signale l'échec d'un DAG (60-observabilite.md) : un coup d'œil quotidien à l'interface Airflow reste nécessaire.
Lire le rapport de dérive
GET /api/v1/monitoring/drift (rôle operateur) rend le dernier rapport par site et une ligne
globale. Le verdict suit cet ordre (app/services/drift.py) :
| Verdict | Condition | Ce que ça veut dire | Quoi faire |
|---|---|---|---|
indetermine |
moins de 24 prévisions vérifiées sur la fenêtre | pas assez de recul pour conclure | attendre ; si ça dure, vérifier ml_score |
derive, couverture |
moins de 80 % des prévisions ont trouvé leur mesure réelle | le pipeline, pas le modèle | vérifier mock_api_import et ml_score dans Airflow |
derive, erreur |
MAE au-delà de 1,25 fois celle de la fenêtre de référence | le modèle se trompe plus qu'avant | réentraîner (ci-dessous) |
derive, biais |
biais absolu au-delà du seuil, désactivé par défaut | le modèle se trompe toujours du même côté | réentraîner, après en avoir cherché la cause dans les données |
stable |
aucune des conditions précédentes | rien à faire | - |
Réentraîner sans perdre le modèle en service
ml_train reste manuel parce que train.py écrase le modèle sans comparer ses métriques à
celles de l'ancien (10-infra.md). Garder une copie avant de lancer :
$compose exec airflow-scheduler cp /opt/ml/state/models/lightgbm-consumption.txt \
/opt/ml/state/models/lightgbm-consumption.txt.avant
$compose exec airflow-apiserver airflow dags trigger ml_train
Comparer ensuite les métriques des deux derniers runs, que train.py enregistre dans le magasin
MLflow du volume (/opt/ml/state/mlflow.db). Aucune interface MLflow n'est servie sur la
machine : ml/README.md décrit mlflow ui. Si le nouveau modèle est moins bon, remettre la
copie en place : le prochain ml_score l'utilisera.
Rétention et archives
retention supprime de la base les chunks de reading plus vieux que
APP_READING_RETENTION_DAYS (1 095 jours), après les avoir exportés en CSV gzip dans le
bucket Garage de l'environnement, chiffrés en SSE-C. Il est idempotent : une reprise ne réécrit
pas un objet déjà exporté et ne retrouve plus un chunk déjà supprimé.
- La clé
GARAGE_SSE_KEYdu.envest la seule qui déchiffre les archives. Garage ne la garde pas. Perdue, les archives sont illisibles : elle se sauvegarde hors de la machine. - Restaurer un chunk :
get_objectavec la clé SSE-C,gunzip, puisCOPY reading FROM STDIN CSV HEADER. La contrainteuq_reading_sourcerefuse les doublons (ADR 0019).
Reprendre après un incident
| Situation | Reprise |
|---|---|
mock_api_import a manqué des heures |
relancer à la main avec --start-time et --end-time sur la fenêtre manquante ; le module refuse toute fenêtre qui recouvre le CSV historique |
alertes a échoué |
relancer : les deux tâches sont idempotentes (ON CONFLICT DO NOTHING) |
derive a échoué |
relancer : un index d'unicité par fenêtre empêche les doublons |
retention a échoué |
relancer : idempotent, voir plus haut |
ml_score en échec répété |
lire les journaux du scheduler ; deux tentatives et un plafond de 30 minutes par passage |