9.5 KiB
ADR-0003 — Recherche full-text avec SQLite FTS5
- Statut : accepted
- Date : 2026-01-27
Contexte
HomeStock nécessite une fonctionnalité de recherche rapide et efficace pour retrouver des objets dans l'inventaire. Les utilisateurs doivent pouvoir rechercher par :
- Nom de l'objet (ex: "perceuse")
- Description (ex: "outil électrique sans fil")
- Catégorie (ex: "bricolage")
- Localisation (ex: "garage", "étagère A")
Contraintes identifiées :
- Volume de données : ~1000-5000 items attendus initialement, maximum 10000 items à long terme
- Performance : Résultats de recherche en <200ms souhaités
- Simplicité : Pas de serveur externe supplémentaire à gérer
- Pertinence : Recherche "fuzzy" pas nécessaire, recherche exacte par mots-clés suffit
- Langue : Recherche en français uniquement
Le choix de la solution de recherche impacte directement l'expérience utilisateur (fonctionnalité critique) et l'architecture technique (composant additionnel ou intégré).
Décision
Nous utilisons SQLite FTS5 (Full-Text Search 5) pour la recherche full-text avec les caractéristiques suivantes :
Configuration
- Table virtuelle FTS5 :
fts_itemscontenant les champs indexés - Champs indexés :
Item.name,Item.description,Category.name,Location.path - Tokenizer :
unicode61(support Unicode, casse insensible) - Synchronisation : Triggers SQLite pour maintenir FTS5 à jour lors des INSERT/UPDATE/DELETE
Exemple de structure
CREATE VIRTUAL TABLE fts_items USING fts5(
item_id UNINDEXED,
name,
description,
category_name,
location_path,
tokenize = 'unicode61'
);
-- Triggers pour synchronisation automatique
CREATE TRIGGER items_after_insert AFTER INSERT ON items BEGIN
INSERT INTO fts_items(item_id, name, description, category_name, location_path)
VALUES (
NEW.id,
NEW.name,
NEW.description,
(SELECT name FROM categories WHERE id = NEW.category_id),
(SELECT path FROM locations WHERE id = NEW.location_id)
);
END;
-- Triggers similaires pour UPDATE et DELETE
Requêtes de recherche
-- Recherche simple
SELECT items.* FROM items
JOIN fts_items ON items.id = fts_items.item_id
WHERE fts_items MATCH 'perceuse';
-- Recherche avec boost (priorité sur le nom)
SELECT items.* FROM items
JOIN fts_items ON items.id = fts_items.item_id
WHERE fts_items MATCH '{name}: perceuse OR {description}: perceuse'
ORDER BY rank;
Alternatives considérées
1. Elasticsearch
Description : Moteur de recherche distribué dédié, standard de l'industrie
Avantages :
- ✅ Recherche extrêmement performante et flexible
- ✅ Recherche fuzzy, phonétique, synonymes
- ✅ Agrégations et analytics avancées
- ✅ Scalable horizontalement
Inconvénients :
- ❌ Serveur Java lourd (1-2GB RAM minimum)
- ❌ Complexité opérationnelle importante
- ❌ Nécessite synchronisation BDD → Elasticsearch
- ❌ Overhead massif pour 1000-10000 items
- ❌ Configuration complexe pour français
Verdict : ❌ Rejeté - Tuer une mouche avec un bazooka. Elasticsearch est conçu pour des millions de documents et des cas d'usage complexes (facettes, agrégations, etc.) dont HomeStock n'a pas besoin.
2. MeiliSearch
Description : Moteur de recherche moderne, léger, orienté UX
Avantages :
- ✅ Très rapide et pertinent out-of-the-box
- ✅ API REST simple
- ✅ Typo-tolerance native
- ✅ Plus léger qu'Elasticsearch (~50MB RAM)
- ✅ Configuration minimale
Inconvénients :
- ❌ Service externe supplémentaire à déployer
- ❌ Synchronisation BDD → MeiliSearch nécessaire
- ❌ Overhead pour petit volume de données
- ❌ Dépendance additionnelle à maintenir
Verdict : ⚠️ Rejeté mais intéressant - Excellente solution technique mais ajoute de la complexité pour un bénéfice limité sur <10k items. À considérer si passage à >50k items.
3. PostgreSQL Full-Text Search (pg_trgm + GIN)
Description : Recherche full-text native de PostgreSQL
Avantages :
- ✅ Très performant avec index GIN
- ✅ Recherche trigram (similitude)
- ✅ Support langues naturelles (stemming français)
- ✅ Pas de service externe
Inconvénients :
- ❌ Nécessite PostgreSQL (vs SQLite choisi dans ADR-0001)
- ❌ Serveur BDD à gérer
- ❌ Complexité setup (dictionnaires français, configuration)
Verdict : ❌ Rejeté - Excellente solution technique mais nécessite PostgreSQL. Si migration vers PostgreSQL (cas multi-utilisateurs), reconsidérer.
4. LIKE '%keyword%' en SQL
Description : Recherche basique avec opérateur LIKE
Avantages :
- ✅ Extrêmement simple
- ✅ Aucune dépendance
Inconvénients :
- ❌ Performance catastrophique (full table scan)
- ❌ Pas de pertinence/ranking
- ❌ Impossible de rechercher sur plusieurs champs efficacement
- ❌ Pas d'opérateurs (AND, OR, NOT)
Verdict : ❌ Rejeté - Inacceptable même pour 1000 items, expérience utilisateur dégradée
5. SQLite FTS5 (notre choix)
Description : Module de recherche full-text intégré à SQLite
Avantages :
- ✅ Intégré à SQLite, pas de service externe
- ✅ Performance excellente pour <100k documents
- ✅ Support opérateurs (AND, OR, NOT, NEAR, phrases)
- ✅ Ranking par pertinence (BM25)
- ✅ Triggers pour synchronisation automatique
- ✅ Tokenizer Unicode (support français)
- ✅ Footprint mémoire minimal
Inconvénients :
- ⚠️ Pas de recherche fuzzy native (typos)
- ⚠️ Pas de stemming français parfait
- ⚠️ Limitations si >100k documents (acceptable pour notre cas)
Verdict : ✅ Choisi - Solution optimale pour notre contexte : performance suffisante, simplicité maximale, pas de dépendance externe
Conséquences
Positives
- Simplicité architecture : Pas de service externe, tout dans SQLite
- Performance suffisante : FTS5 recherche <50ms sur 10k items
- Synchronisation automatique : Triggers maintiennent l'index à jour
- Zéro configuration : FTS5 inclus dans SQLite depuis version 3.9.0 (2015)
- Backup simplifié : Index FTS5 dans le même fichier .db que les données
- Opérateurs avancés : Support AND, OR, NOT, recherche par phrase
- Ranking pertinent : Algorithme BM25 pour trier par pertinence
Négatives
- Pas de typo-tolerance : "perceuze" ne trouvera pas "perceuse"
- Stemming limité : "perçant" et "percer" non reliés automatiquement
- Performance dégradée >100k items : Limite théorique (très au-delà de notre besoin)
- Pas d'agrégations : Impossible de faire des facettes de recherche complexes
- Recherche française limitée : Pas de dictionnaire français sophistiqué
Mitigations
- Typo-tolerance : Implémenter suggestions de correction côté application si besoin (Levenshtein distance)
- Stemming : Ajouter mots-clés/tags aux items pour améliorer découvrabilité
- Performance : Si >100k items, migrer vers PostgreSQL pg_trgm ou MeiliSearch
- Agrégations : Implémenter filtres côté application (par catégorie, localisation, etc.)
Impacts techniques
Développement
- Migration Alembic : Créer table virtuelle FTS5 + triggers lors du setup initial
- Service de recherche :
search_service.pyencapsule la logique FTS5 - API endpoint :
GET /api/v1/search?q=keywordretourne items matchant
Performance attendue
- <1000 items : <10ms
- 1000-5000 items : <50ms
- 5000-10000 items : <150ms
- >10000 items : Dégrédation possible, monitoring nécessaire
Structure de code
# services/search_service.py
class SearchService:
async def search_items(self, query: str, limit: int = 50):
"""Recherche full-text avec FTS5"""
# Sanitize query (échapper caractères spéciaux)
safe_query = self._escape_fts5_query(query)
# Query FTS5 avec ranking
sql = """
SELECT items.*,
rank AS relevance
FROM items
JOIN fts_items ON items.id = fts_items.item_id
WHERE fts_items MATCH :query
ORDER BY rank
LIMIT :limit
"""
return await self.db.execute(sql, {"query": safe_query, "limit": limit})
Évolution future
Si les limitations deviennent bloquantes :
- Migration PostgreSQL : Activer pg_trgm pour recherche similitude
- Ajout MeiliSearch : Service séparé pour recherche avancée (garder SQLite pour données)
- Hybrid search : Combiner FTS5 (rapide) + recherche sémantique externe (typos, synonymes)
Notes
SQLite FTS5 est utilisé par de nombreux projets à succès pour des cas d'usage similaires :
- Firefox : Recherche dans l'historique et favoris
- Apple Mail : Indexation locale des emails
- VS Code : Recherche dans l'historique de commandes
Pour HomeStock avec <10k items attendus, FTS5 est largement suffisant et offre le meilleur ratio performance/simplicité.
La décision de ne pas utiliser Elasticsearch ou MeiliSearch n'est pas dogmatique : si le projet évolue vers >50k items ou nécessite recherche fuzzy sophistiquée, migration possible sans refonte majeure (API de recherche reste identique, seule l'implémentation change).
Principe appliqué : "Use the simplest tool that works" - Ne pas sur-architecturer pour des besoins hypothétiques futurs.
Contributeurs : Gilles (décideur) + Claude Code (architecte)
Références :
- SQLite FTS5 documentation : https://www.sqlite.org/fts5.html
- BM25 ranking algorithm : https://en.wikipedia.org/wiki/Okapi_BM25