From 8819d04cf240e963624b7b0be0b22408cd586a9e Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Thu, 4 Jun 2026 07:33:50 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20indexation=20invers=C3=A9e=20non-bloquan?= =?UTF-8?q?te=20=E2=80=94=20run=5Fin=5Fexecutor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'étape 'Building inverted index...' au démarrage bloquait l'event loop asyncio, empêchant le serveur de répondre aux requêtes HTTP (navigation, refresh) jusqu'à la fin de l'indexation. - init_inverted_index() exécuté dans ThreadPoolExecutor via run_in_executor - get_inverted_index() ne fait plus de rebuild auto bloquant - Le serveur répond immédiatement, même pendant l'indexation --- ROADMAP.md | 8 ++++++++ backend/main.py | 7 +++++-- backend/search.py | 13 ++++++------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index bf0dc87..3ee4a77 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -172,6 +172,14 @@ - [x] **Mise à jour automatique** : quand on clique sur un dossier dans l'arborescence, la page se met à jour - [x] **Déclenchement** : affiché automatiquement quand un vault spécifique est sélectionné +### ✅ 12. Indexation non-bloquante — FAIT +- **Effort** : 0.5 jour | **Impact** : 🟡 +- L'étape "Building inverted index..." au démarrage bloquait l'event loop asyncio, empêchant le serveur de répondre aux requêtes HTTP (navigation, refresh) jusqu'à la fin de l'indexation. +- **Fichiers** : `backend/main.py`, `backend/search.py` +- [x] **`init_inverted_index()` dans `run_in_executor`** : exécuté dans le `ThreadPoolExecutor` au lieu de la boucle asyncio principale +- [x] **`get_inverted_index()` sans rebuild auto** : ne déclenche plus de rebuild bloquant — retourne l'index vide si pas encore prêt (les recherches retournent 0 résultats jusqu'à la fin de l'indexation) +- [x] **Navigation + refresh fonctionnels immédiatement** : le serveur répond aux requêtes HTTP dès le démarrage, même pendant l'indexation + --- ## 📋 Backlog (P3/P4 — Long terme) diff --git a/backend/main.py b/backend/main.py index 7b70738..1040f42 100644 --- a/backend/main.py +++ b/backend/main.py @@ -573,8 +573,11 @@ async def lifespan(app: FastAPI): logger.info("Background indexing started") await build_index(_progress_cb) - # Build inverted index for search (one-time, then incremental) - init_inverted_index() + # Build inverted index in a thread pool to avoid blocking the event loop. + # The inverted index rebuild is CPU-bound (tokenization, indexing) and + # would freeze HTTP responses if run in the async event loop. + loop = asyncio.get_running_loop() + await loop.run_in_executor(_search_executor, init_inverted_index) # Start file watcher config = _load_config() diff --git a/backend/search.py b/backend/search.py index 6a98e05..c48693f 100644 --- a/backend/search.py +++ b/backend/search.py @@ -672,14 +672,13 @@ def init_inverted_index(): def get_inverted_index() -> InvertedIndex: """Return the singleton inverted index. - Auto-builds on first call if the index has files but the inverted - index hasn't been built yet (fallback for paths that don't go through - ``init_inverted_index()``). + The inverted index is built once at startup in a background thread. + If called before the build completes, returns an empty index (searches + will return no results until the build finishes). + + No automatic rebuild — the startup sequence handles the initial build. + Incremental updates are applied via ``update_inverted_index()``. """ - if _inverted_index.doc_count == 0 and any( - vdata.get("files") for vdata in index.values() - ): - _inverted_index.rebuild() return _inverted_index