docs(hermes-tt): runbook livelock sweep attachments (scan_fail_count)
This commit is contained in:
@@ -0,0 +1,116 @@
|
|||||||
|
# nyora-notes-tt : livelock du sweep attachments (ORDER BY + scan_fail_count)
|
||||||
|
|
||||||
|
**Date** : 08/08/2026
|
||||||
|
**Instance** : hermes-tt
|
||||||
|
**Contexte** : suite chantier liaison Mail↔RAG↔OneDrive (cf `nyora-notes-tt-attachments-liaison-onedrive-07-08-2026.md`)
|
||||||
|
|
||||||
|
## Symptôme
|
||||||
|
|
||||||
|
`run_batch_sweep_all_years.sh` lancé le 07/08 à 21h58 (relance après un premier arrêt à 66 notes
|
||||||
|
restantes). Tourne ses 15 lots max (`MAX_BATCHES=15`) sans erreur de code retour, mais **la
|
||||||
|
progression réelle s'arrête après le lot 1** :
|
||||||
|
|
||||||
|
```
|
||||||
|
Lot 1 : 66 notes restantes
|
||||||
|
Lot 2 : 49 notes restantes <- 17 notes traitées, OK
|
||||||
|
Lot 3 à 15 : 49 notes restantes <- AUCUN progrès pendant 13 lots, ~1h35 perdues
|
||||||
|
```
|
||||||
|
|
||||||
|
Chaque lot se termine par :
|
||||||
|
```
|
||||||
|
[ABORT] 3 echecs consecutifs -- session probablement degradee, arret propre de ce passage
|
||||||
|
(3/20 notes vues, 3 non verifiees, seront retentees au prochain passage)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cause racine
|
||||||
|
|
||||||
|
`download_attachments_phase1.py::main()` :
|
||||||
|
|
||||||
|
```python
|
||||||
|
q = "SELECT id, folder_path, message_id, subject FROM notes WHERE attachments_scanned = 0"
|
||||||
|
q += " ORDER BY folder_path"
|
||||||
|
```
|
||||||
|
|
||||||
|
+ boucle qui incrémente `consecutive_failures` sur chaque note en échec transitoire, et
|
||||||
|
**avorte le lot dès `MAX_CONSECUTIVE_TRANSIENT_FAILURES=3`** (design volontaire : évite de
|
||||||
|
tourner à vide sur une session OWA dégradée).
|
||||||
|
|
||||||
|
Or 3 notes précises timeoutent **systématiquement** au listing PJ côté OWA :
|
||||||
|
- `note_8daed11d2468` (dossier `00 2026|DR Zone Sud|Tataouine`, index 17)
|
||||||
|
- `note_3bbba52d709b` (dossier `02 2024|Hors budget|Sfax`, index 15)
|
||||||
|
- `note_8c56696597e7` (dossier `02 2024|Hors budget|Sfax`, index 17)
|
||||||
|
|
||||||
|
Cause OWA elle-même non creusée (PJ volumineuse/corrompue possible côté serveur mail) — hors
|
||||||
|
scope de ce fix, qui traite le symptôme côté pipeline plutôt que la cause OWA.
|
||||||
|
|
||||||
|
Ces 3 notes ne sont **jamais marquées `attachments_scanned=1`** en cas d'échec (design
|
||||||
|
volontaire pour permettre un retry ultérieur — voir commentaire ligne ~156 du script :
|
||||||
|
*"on ne marque surtout pas attachments_scanned. La note sera retentee au prochain passage"*).
|
||||||
|
|
||||||
|
Combiné à un tri déterministe par `folder_path`, ces 3 notes reviennent **systématiquement en
|
||||||
|
tête de la requête** à chaque lot, et l'abort à 3 échecs consécutifs empêche structurellement
|
||||||
|
d'atteindre les 46 autres notes en attente derrière elles. Un livelock classique : le
|
||||||
|
mécanisme de sécurité (retry sur échec transitoire) et le mécanisme de protection (abort après
|
||||||
|
3 échecs) se combinent pour bloquer toute progression, indéfiniment, peu importe le nombre de
|
||||||
|
lots relancés.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
1. **Schéma** : `ALTER TABLE notes ADD COLUMN scan_fail_count INTEGER DEFAULT 0` +
|
||||||
|
`PRAGMA wal_checkpoint(TRUNCATE)` avant tout redémarrage de conteneur (piège déjà documenté
|
||||||
|
dans le runbook du 07/08 — DDL perdue sans checkpoint).
|
||||||
|
|
||||||
|
2. **Code** (`download_attachments_phase1.py`) :
|
||||||
|
- Requête : `ORDER BY scan_fail_count ASC, folder_path ASC` (au lieu de `ORDER BY folder_path`
|
||||||
|
seul).
|
||||||
|
- Dans la boucle principale, sur `result is None` :
|
||||||
|
```python
|
||||||
|
conn.execute("UPDATE notes SET scan_fail_count = scan_fail_count + 1 WHERE id = ?", (note[0],))
|
||||||
|
conn.commit()
|
||||||
|
```
|
||||||
|
|
||||||
|
Effet : une note qui échoue une fois passe de `scan_fail_count=0` à `1` et se retrouve
|
||||||
|
derrière toutes les notes fraîches (`scan_fail_count=0`) au lot suivant. Elle reste
|
||||||
|
éligible au retry (jamais `attachments_scanned=1`), mais ne bloque plus la file — elle
|
||||||
|
remonte automatiquement en tête une fois que toutes les autres notes auront, elles aussi,
|
||||||
|
échoué au moins une fois (auto-équilibrage, pas de purge définitive).
|
||||||
|
|
||||||
|
3. **Déploiement** : `docker compose build nyora-notes-tt` (fichiers baked-in via `COPY . .`,
|
||||||
|
jamais montés en volume) puis `docker compose up -d nyora-notes-tt` — **jamais
|
||||||
|
`docker restart`**, qui relance l'image figée sans le nouveau code (piège déjà documenté).
|
||||||
|
|
||||||
|
## Vérification
|
||||||
|
|
||||||
|
Lot 1 post-fix : réavorte sur les 3 mêmes notes (attendu — `scan_fail_count` était à 0 pour
|
||||||
|
tout le monde au premier essai, donc égalité avec `folder_path` comme avant). Mais chacune
|
||||||
|
passe alors à `scan_fail_count=1`.
|
||||||
|
|
||||||
|
Lot 2 post-fix : les 3 notes sautées, le scan attaque directement de nouveaux dossiers
|
||||||
|
(`Modernisation du réseau FBB`, `Moi`, `RLA|AO 58/2024`...). **49 → 29 notes restantes en un
|
||||||
|
seul lot.** Livelock confirmé résolu.
|
||||||
|
|
||||||
|
## Bonus (effet de bord positif)
|
||||||
|
|
||||||
|
Le rebuild déclenché par ce fix a aussi **figé en dur** dans l'image le correctif
|
||||||
|
`embed_attachments.py` (chargement de l'extension `vec0` avant la première requête sur
|
||||||
|
`attachments_vec`) qui n'existait jusque-là que comme patch live (`docker cp`) sur le
|
||||||
|
conteneur en cours d'exécution — donc perdu à chaque `docker compose up -d` sans rebuild
|
||||||
|
intermédiaire. C'est désormais définitivement corrigé côté source ET image.
|
||||||
|
|
||||||
|
## Décisions actées, ne pas rouvrir
|
||||||
|
|
||||||
|
- Ne pas investiguer la cause OWA des 3 timeouts sauf si le phénomène s'étend à d'autres
|
||||||
|
notes après un cycle complet de sweep (dans ce cas, creuser côté `hermes-mail-browser` /
|
||||||
|
taille des PJ concernées).
|
||||||
|
- Ne pas remplacer le mécanisme d'abort à 3 échecs consécutifs (protection utile contre une
|
||||||
|
vraie dégradation de session OWA) — le fix agit sur l'ordre de la file, pas sur le seuil
|
||||||
|
d'abort.
|
||||||
|
- `scan_fail_count` n'a pas de plafond/purge : une note qui échoue indéfiniment restera
|
||||||
|
éligible au retry pour toujours, juste toujours en dernière position. Acceptable tant que
|
||||||
|
le nombre de notes structurellement bloquées reste marginal (3/49 ici).
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
- `/mnt/docker/nyora-notes-tt/download_attachments_phase1.py` (ORDER BY + increment
|
||||||
|
scan_fail_count)
|
||||||
|
- DB `nyora-notes-tt.db` : colonne `notes.scan_fail_count` ajoutée
|
||||||
Reference in New Issue
Block a user