Aller au contenu

Déployer Qdrant local pour recherche sémantique interne

Déployer Qdrant local pour recherche sémantique interne

Section intitulée « Déployer Qdrant local pour recherche sémantique interne »

Une base vectorielle locale stocke les représentations numériques des documents et retrouve les passages proches d’une requête. Les principes d’embeddings et de recherche sémantique étant déjà posés, cette page traite du moteur, de son index, de son exploitation et de ce qui casse quand le volume cesse d’être décoratif.

→ Lire l’explication complète de la marmotte

Choisir Qdrant, Milvus ou Faiss selon la contrainte réelle

Section intitulée « Choisir Qdrant, Milvus ou Faiss selon la contrainte réelle »

Le compromis central est brutal : réduire le temps de recherche impose d’accepter qu’un résultat très proche puisse parfois ne pas être retourné. La recherche de voisinage approximatif, ANN (Approximate Nearest Neighbor), échange donc une fraction mesurable de précision contre une latence et un coût matériel maîtrisables. Refuser ce compromis revient à comparer chaque requête à tous les vecteurs, ce qui fonctionne parfaitement jusqu’au jour où cela ne fonctionne plus du tout.

Pour une recherche sémantique interne auto-hébergée, Qdrant est le choix par défaut. Il offre un serveur persistant, une interface HTTP (Hypertext Transfer Protocol) et gRPC (Google Remote Procedure Call), des filtres sur métadonnées, des snapshots cohérents et un index HNSW (Hierarchical Navigable Small World) exploitable sans transformer l’infrastructure en élevage de composants distribués.

Choisir Qdrant lorsque les données tiennent sur un ou quelques nœuds, que les filtres métier comptent, et qu’une équipe infrastructure doit restaurer le service à 03:00 sans réciter une thèse sur les partitions distribuées. Une collection de quelques centaines de milliers à plusieurs dizaines de millions de vecteurs relève de ce périmètre, sous réserve de dimension, de mémoire disponible et de charge concurrente.

Milvus devient rationnel pour des volumes qui imposent réellement une distribution des données, une forte ingestion parallèle ou plusieurs téraoctets de vecteurs. Il paie cette capacité par davantage de dépendances et d’opérations : stockage objet, métadonnées, coordination et observabilité de plusieurs services. Le cluster distribué n’est pas une preuve de maturité, seulement la réponse à un problème de taille. Les slides de vendeurs confondent volontiers les deux.

Faiss reste une bibliothèque, pas une base de données. Elle convient à un processus Python ou C++ contrôlé, à un index embarqué en lecture seule, ou à des expérimentations où l’application possède déjà la persistance, le filtrage, les sauvegardes et la synchronisation. Dès qu’il faut des mises à jour concurrentes, des métadonnées filtrables et une restauration fiable, Faiss déplace le problème dans le code applicatif. Ce déplacement finit souvent par produire une base vectorielle artisanale, avec la qualité habituelle des projets qui « ne font qu’un petit index ».

Pré requis Qdrant et isolation de la surface d’attaque

Section intitulée « Pré requis Qdrant et isolation de la surface d’attaque »

Qdrant doit disposer d’un volume persistant local ou bloc, avec des écritures durables activées par la plateforme. Un disque réseau à latence variable dégrade directement les insertions et les reconstructions d’index. Prévoir la mémoire pour les vecteurs et l’index HNSW, puis mesurer : dimension du vecteur, nombre de points, paramètres HNSW et segments déterminent la consommation réelle.

Le modèle d’embeddings doit être figé par collection : même dimension, même métrique, même normalisation. Mélanger des vecteurs issus de modèles distincts dans une collection rend les distances dépourvues de sens. Une migration de modèle crée une nouvelle collection, suivie d’une double alimentation et d’un basculement explicite. Réencoder silencieusement seulement une partie des documents est une corruption fonctionnelle, pas une optimisation.

Exposer Qdrant directement sur un réseau utilisateur est une erreur. Le moteur contient des textes, des métadonnées et parfois des fragments de secrets qui auraient dû être retirés avant ingestion. L’API doit être placée derrière un proxy interne avec Transport Layer Security (TLS), authentification, limitation de débit et journalisation. Le jeton d’accès Qdrant doit être stocké dans le gestionnaire de secrets de la plateforme, jamais dans une image de conteneur, une variable de pipeline visible ou un fichier .env offert au premier dump de diagnostic.

La base vectorielle ne garantit pas la confidentialité du contenu indexé. Un vecteur n’est pas un chiffrement et il peut révéler des proximités, catégories ou attributs de documents. Elle ne garantit pas non plus l’autorisation documentaire : filtrer les résultats après recherche peut déjà exposer des informations par score, latence ou métadonnées. Les droits d’accès doivent être traduits en filtres appliqués pendant la recherche, avec des champs d’autorisation contrôlés côté serveur applicatif. Pour les principes d’architecture locale, voir IA locale et l’architecture cyber.

Épingler la version de l’image et monter /qdrant/storage sur un volume dédié. Le tag flottant latest transforme chaque redémarrage en expérience de laboratoire, ce qui manque rarement de piquant le jour d’un incident.

L’exemple suivant crée une collection de vecteurs normalisés de 768 dimensions, recherchés avec la distance cosinus. L’authentification est activée ; le proxy TLS reste extérieur au conteneur et ne doit pas être confondu avec un port publié sur toutes les interfaces.

# compose.yaml : exécution locale, port limité à l'interface de bouclage.
services:
qdrant:
image: qdrant/qdrant:v1.12.5 # Épingler et valider toute montée de version.
restart: unless-stopped
ports:
- "127.0.0.1:6333:6333" # Le proxy interne seul accède ensuite au service.
environment:
QDRANT__SERVICE__API_KEY: "${QDRANT_API_KEY}" # Secret injecté au démarrage.
volumes:
- /srv/qdrant/storage:/qdrant/storage
# Création de collection, après export de QDRANT_API_KEY dans un gestionnaire de secrets.
# curl -X PUT http://127.0.0.1:6333/collections/docs-v1 \
# -H "api-key: ${QDRANT_API_KEY}" -H "Content-Type: application/json" \
# -d '{"vectors":{"size":768,"distance":"Cosine"},
# "hnsw_config":{"m":16,"ef_construct":128},
# "optimizers_config":{"indexing_threshold":20000}}'

Créer des index de charge utile, ou payload indexes, pour chaque champ fréquemment filtré : source, classification, tenant_id, allowed_group. Sans index de filtre, une requête sémantique assortie d’une autorisation peut se transformer en balayage coûteux. Un filtre de droits doit aussi éviter les listes de groupes gigantesques dans chaque point : préférer des identifiants de périmètre compacts et une résolution d’autorisation en amont lorsque le modèle IAM (Identity and Access Management) le permet.

HNSW relie chaque vecteur à des voisins dans un graphe multi-niveaux. Le paramètre m fixe approximativement le nombre de connexions par point : l’augmenter améliore le rappel, c’est-à-dire la probabilité de retrouver les vrais voisins, mais consomme davantage de mémoire. m=16 est un point de départ sérieux ; m=32 se justifie lorsque la qualité mesurée reste insuffisante et que la mémoire n’est pas déjà la contrainte.

ef_construct contrôle l’effort lors de la construction de l’index. Une valeur de 128 convient souvent ; 256 augmente le coût d’ingestion et peut améliorer le graphe. Il ne faut pas régler ce paramètre au flair : constituer un jeu de requêtes annotées, comparer les résultats ANN à une recherche exhaustive sur un échantillon, puis mesurer le rappel à k, le nombre de résultats demandés.

hnsw_ef pilote l’effort à la requête. Plus il est élevé, plus le moteur visite de candidats, améliore le rappel et augmente la latence. Commencer à 64, mesurer les percentiles p50, p95 et p99, puis monter à 128 si le rappel évalué l’exige. Régler hnsw_ef à plusieurs milliers « pour être sûr » revient à financer une recherche exhaustive avec le vocabulaire de l’approximation.

Les filtres réduisent le nombre de candidats valides. Une requête très filtrée peut exiger un hnsw_ef supérieur, ou justifier un index spécialisé sur le champ de filtre. Mesurer séparément les requêtes sans filtre, avec filtre courant et avec filtre sélectif. Une moyenne globale masque exactement le cas qui finit par saturer les workers.

Ingestion incrémentale, suppression et évolution des documents

Section intitulée « Ingestion incrémentale, suppression et évolution des documents »

Chaque fragment indexé doit porter un identifiant déterministe dérivé de la source, de sa version et de sa position. Un upsert, insertion ou mise à jour par identifiant, remplace alors le vecteur du fragment modifié sans reconstruire toute la collection. Conserver dans la charge utile le hachage du contenu, la version du modèle, la date d’indexation et l’identifiant de contrôle d’accès.

À l’arrivée d’un document, découper, calculer les vecteurs, puis envoyer les points par lots. La taille du lot dépend de la mémoire du client et de la limite du proxy, pas d’une superstition héritée d’un notebook. Valider l’accusé d’écriture avant de publier la nouvelle version documentaire. En cas d’échec partiel, rejouer le lot avec les mêmes identifiants : l’opération reste idempotente.

Pour une suppression, supprimer explicitement les points ou appliquer un marqueur d’état exclu par filtre. Le marqueur facilite une purge asynchrone, mais ne remplace pas la suppression si une obligation de retrait porte sur les données stockées. Les tombstones et segments fragmentés imposent périodiquement une optimisation contrôlée. Ne pas lancer une reconstruction lors du pic de recherche : le système répondrait peut-être, mais avec l’enthousiasme d’un serveur de fichiers sous analyse antivirus.

Sauvegarde, restauration et supervision de l’index vectoriel

Section intitulée « Sauvegarde, restauration et supervision de l’index vectoriel »

Un snapshot Qdrant contient les données et la structure nécessaire à la restauration de la collection. Planifier des snapshots cohérents, les copier vers un stockage distinct, chiffré et soumis à une politique de rétention. La clé de chiffrement du stockage de sauvegarde doit être séparée des identifiants d’accès au moteur ; une sauvegarde accessible avec les mêmes secrets que la production ne constitue pas une frontière, juste une seconde porte avec la même clé.

Tester la restauration sur une instance isolée : créer une collection restaurée, vérifier le nombre de points, les index de charge utile, les requêtes de référence et les contrôles d’accès. Un snapshot non restauré est une croyance. Les vecteurs doivent pouvoir être régénérés depuis les sources documentaires, car une sauvegarde ne corrige ni un mauvais modèle ni une ingestion empoisonnée.

Superviser l’état du cluster, l’espace disque, la mémoire résidente, les erreurs d’écriture, la taille des segments et la durée des optimisations. Pour la recherche, exporter latence p50, p95, p99, taux d’erreur, nombre de points scannés, taille des réponses et rappel sur un jeu de contrôle exécuté périodiquement. Une hausse lente du p95 à volume croissant indique souvent un hnsw_ef trop ambitieux, des filtres non indexés, une saturation mémoire ou une fragmentation de segments.

Définir un seuil d’alerte sur la dérive, pas seulement sur une panne franche. Une recherche qui passe de 40 à 250 millisecondes sans erreur n’alerte aucun tableau vert, mais elle finit par épuiser les délais du service appelant. La dégradation progressive est le mode de défaillance favori des index : suffisamment lente pour passer les revues, suffisamment rapide pour ruiner une démonstration.

Sans moteur vectoriel interne, la recherche sémantique se reporte ailleurs : sur des API externes qui reçoivent les extraits de documents, ou sur des copies locales dispersées dans des postes et des notebooks, hors de toute rétention et de toute purge. Ce qui disparaît d’abord n’est pas la performance, c’est la traçabilité : aucun journal centralisé des requêtes, donc aucun moyen de reconstituer quels passages ont été restitués, à qui, ni de détecter une extraction massive par requêtes successives. Un retrait de données devient inapplicable, faute de point unique où supprimer les fragments. Enfin, un service de recherche qui répond différemment selon l’existence d’un document devient un oracle, mécanisme déjà documenté dans la brèche « la question qui rend le module bavard ».

Le système commence par transformer chaque morceau de document en une longue liste de nombres. Cette liste est produite par un modèle choisi à l’avance. Tous les documents d’une même collection doivent utiliser le même modèle, sinon les distances entre listes ne veulent plus dire grand-chose.

La base stocke chaque liste avec le texte d’origine et des informations comme la source, la date et les droits d’accès. Quand une question arrive, le même modèle produit une liste de nombres pour cette question. La base cherche alors les listes les plus proches, sans comparer forcément toute la collection. Cette recherche plus rapide peut rater certains bons résultats : il faut donc mesurer régulièrement la qualité obtenue et le temps de réponse.

Quand un document change, seuls ses morceaux modifiés sont recalculés et remplacés grâce à des identifiants stables. Quand un modèle change, il faut créer une nouvelle collection et recalculer tous les documents. Mélanger ancien et nouveau calcul fausse les résultats.

Le service doit être protégé par une authentification, un réseau limité et des droits vérifiés avant de renvoyer un texte. Il faut aussi copier régulièrement les données dans une sauvegarde testée. Si le disque se remplit, si la mémoire manque ou si les recherches deviennent trop lentes, les mesures de temps et d’erreurs permettent de détecter le problème avant que le service ne cesse de répondre.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.