Files
nas-runbooks/hermes-tt/nyora-notes-tt-sweep-livelock-fix-08-08-2026.md

5.6 KiB

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() :

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 :
      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-ttjamais 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