5.8 KiB
nyora-notes-tt : disk I/O error sur toutes les requetes apres chaque backup (moteur SQLite mixte sur DB WAL live)
Instance auteur : hermes-tt Date : 2026-08-02 Tags : nyora-notes-tt, sqlite, wal, sqlean, backup Statut : valide
Probleme
Apres la fin propre de l'ingestion Phase 1 (205 dossiers, 393 notes, cron watchdog retire), tous les tools MCP RAG mail (search_mail_memory, get_checkpoint_status, /pending-folders) tombaient en erreur des le premier appel :
sqlean.dbapi2.OperationalError: disk I/O error
File "/app/mcp_server.py", line 336, in pending_folders
conn = get_db_connection()
File "/app/setup_db.py", line 22, in get_db_connection
wal_res = conn.execute("PRAGMA journal_mode = WAL;").fetchone()
Le conteneur restait Up ... (healthy) (le healthcheck ne touche pas la DB en profondeur),
/health repondait 200 en continu -- seules les routes qui ouvrent une vraie connexion DB
echouaient. Pas de piste disque materielle : dmesg propre, /volume1 a 70% (large marge).
Contexte et contraintes
- DB SQLite en mode WAL, moteur sqlean + extension sqlite_vec (embeddings Gemini 768-dim).
get_db_connection()(setup_db.py) ouvre une connexion neuve a chaque appel de route, sans pooling, et sansPRAGMA busy_timeout.- Route
/backup(mcp_server.py,_run_backup()) declenchee par n8n toutes les 2h, utilisaitimport sqlite3 as std_sqlite3-- le module stdlib, different du moteur sqlean utilise par le reste de l'app sur la meme DB live.
Ce qui NE fonctionne PAS
| Tentative | Erreur obtenue | Raison de l'echec |
|---|---|---|
| Redemarrer le conteneur sans toucher au code | Fonctionne temporairement, revient a la prochaine sauvegarde n8n (cycle 2h) | Le vrai declencheur (moteur mixte sur backup) n'est pas traite, juste les fd/connexions purges |
| Ignorer et considerer que c'est un probleme disque NAS | Aucune erreur dmesg, espace disque large | Le diagnostic initial (panne materielle) etait errone -- l'erreur est applicative |
Diagnostic (preuve)
ls -la /proc/1/fd dans le conteneur montrait plusieurs descripteurs vers
nyora-notes-tt.db-wal et nyora-notes-tt.db-shm marques (deleted), horodates pres
des cycles de backup (12:00 / 14:00) :
lrwx------ 1 appuser appuser 64 Aug 2 14:16 10 -> /app/nyora-notes-tt.db-shm (deleted)
lrwx------ 1 appuser appuser 64 Aug 2 14:16 12 -> /app/nyora-notes-tt.db-wal (deleted)
lrwx------ 1 appuser appuser 64 Aug 2 14:16 13 -> /app/nyora-notes-tt.db
Le close() de la connexion stdlib sqlite3 de _run_backup() (checkpoint automatique
en mode WAL) recree le -wal/-shm pendant que les connexions sqlean deja ouvertes
dans le process principal tiennent encore les anciens descripteurs -> les deux moteurs
n'ont plus la meme vue de l'index WAL partage (shared memory) -> disk I/O error sur
toute connexion sqlean ouverte juste apres. L'absence de PRAGMA busy_timeout transformait
la moindre contention en echec dur au lieu d'une attente/retry.
Reproduction fiable : declencher /backup manuellement puis enchainer des appels
/pending-folders -- echec systematique avant le fix, 8/8 OK apres.
Solution validee
setup_db.py, dansget_db_connection(), juste apresconn = sqlite3.connect(db_path):conn.execute("PRAGMA busy_timeout = 5000;")mcp_server.py, dans_run_backup(): remplacer le moteur stdlib par le meme moteur que le reste de l'app, et poser aussi le busy_timeout sur la connexion source :import sqlean as std_sqlite3 # etait: import sqlite3 as std_sqlite3 ... src = std_sqlite3.connect(DB_PATH) src.execute("PRAGMA busy_timeout = 5000;")- Rebuild + recreation du conteneur :
cd /volume1/docker/nyora-notes-tt docker compose build docker compose up -d --force-recreate
Verification
# Declencher un backup manuel puis enchainer des requetes -- scenario exact qui plantait
docker exec nyora-notes-tt python3 -c "import urllib.request; urllib.request.urlopen(urllib.request.Request('http://localhost:8000/backup', method='POST', headers={'x-ingest-key':'<cle>'}))"
for i in 1 2 3 4 5 6 7 8; do
docker exec nyora-notes-tt python3 -c "import urllib.request; print(urllib.request.urlopen('http://localhost:8000/pending-folders?limit=1').status)"
done
# Resultat attendu : 200 x8, y compris immediatement apres le backup
Pieges specifiques DSM / NAS
dockerabsent du PATH en shell SSH non-interactif Synology -- utiliser le chemin complet/usr/local/bin/docker(symlink vers/var/packages/ContainerManager/target/usr/bin/docker).ls -lasur les fichiers host peut afficher des bits POSIX larges (777) alors qu'une ACL Synology (synoacltool -get) restreint reellement l'acces -- ne jamais se fier aux seuls bits POSIX affiches pour diagnostiquer un probleme de permission sur ce NAS.- Depuis l'interieur d'un conteneur (mcp-nas), joindre le service NyoraNotes hermes-perso
(8787) et Gitea (3232) via
172.17.0.1, jamais l'IP LAN192.168.100.33(timeout).
Piege generique (tout futur service Hermes/nyora-notes-* sur sqlean + WAL)
Ne jamais ouvrir une DB SQLite en mode WAL activement utilisee par un process long-vivant
avec un deuxieme moteur/binding SQLite different (stdlib vs sqlean vs autre), meme pour
une operation aussi anodine qu'une sauvegarde en lecture via Connection.backup(). Le
checkpoint automatique au close() peut recreer le -wal/-shm et desynchroniser les
connexions deja ouvertes dans l'autre moteur. Regle : un seul moteur SQLite par DB WAL live,
et toujours PRAGMA busy_timeout des l'ouverture de toute connexion.
References
- Repo nyora-notes-tt : aucun repo Gitea existant au moment du fix (jamais initialise) --
le correctif ne vit que sur le filesystem NAS (
/volume1/docker/nyora-notes-tt/).