Base de données vectorielle : déployer Milvus pour la recherche sémantique
Rechercher un document par mot-clé exact est une méthode obsolète. La recherche sémantique nécessite une base de données vectorielle capable de manipuler des embeddings de haute dimension.
Milvus répond à ce besoin en gérant des milliards de vecteurs avec une latence minimale. L’enjeu est de transformer du texte ou des images en vecteurs numériques exploitables.
Ce guide détaille le déploiement de Milvus, la création de collections et l’exécution de recherches de similarité par distance cosinus.
🛠️ Prérequis
L’environnement doit être stable pour éviter les corruptions d’index lors des tests.
- Docker et Docker Compose (version 2.20+)
- Python 3.11 ou supérieur
- PyMilvus (version 2.3.0+)
- Une machine avec au moins 8 Go de RAM
📚 Comprendre Base de données vectorielle
Une base de données vectorielle ne stocke pas des chaînes de caractères, mais des listes de nombres flottants. Ces nombres représentent des points dans un espace multidimensionnel. La proximité géométrique entre deux points traduit une proximité sémantique entre deux concepts.
Le moteur utilise des algorithmes de type ANN (Approximate Nearest Neighbor). Le plus utilisé est HNSW (Hierarchical Navigable Small World). Contrairement à une recherche exacte sur une table SQL, HNSW construit un graphe pour naviguer rapidement vers les voisins les plus proches.
En Perl, nous utiliserions des structures de données complexes pour simuler cela, mais Milvus délègue cette complexité à des nœuds spécialisés (QueryNode, IndexNode). La distinction entre recherche L2 (Euclidienne) et recherche Cosine est cruciale pour la précision des résultats.
🐪 Le code — Base de données vectorielle
📖 Explication
Dans le premier snippet, l’utilisation de is_primary=True et auto_id=True simplifie la gestion des clés. C’est l’équivalent d’un auto-incrément SQL. Attention, l’utilisation de auto_id empêche de fournir vos propres identifiants métier.
Dans le second snippet, la méthode col.load() est souvent oubliée par les débutants. Sans ce chargement explicite de la collection en mémoire vive, toute tentative de recherche renverra une erreur indiquant que la collection n’est pas prête. C’est une particularité de l’architecture cloud-native de Milvus : les données résident sur le stockage objet (MinIO) et ne sont projetées en RAM que sur demande.
Le paramètre nprobe dans search_params contrôle le nombre de listes de recherche visitées dans l’index IVF. Plus il est élevé, plus la précision augmente, mais plus la latence grimpe. C’est le curseur classique entre performance et précision.
🔄 Second exemple
Tutoriel pas-à-pas
La mise en place commence par l’infrastructure. Milvus n’est pas un simple binaire, c’est un écosystème de microservices. Utilisez Docker Compose pour orchestrer les composants essentiels : Etcd (pour les métadonnées), MinIO (pour le stockage objet) et Pulsar (pour le streaming).
Exécutez la commande suivante pour lancer l’instance :docker-implimentation: docker compose up -d. Attendez environ 60 secondes que les services soient prêts. Vérifiez l’état avec docker ps. Une base de données vectorielle mal configurée au niveau de la mémoire fera crasher le QueryNode dès la première insertion massive.
Étape 2 : Définition du schéma. Dans une base de données vectorielle, la dimension du vecteur est immuable. Si vous avez utilisé un modèle BERT avec 768 dimensions, votre schéma doit impérativement refléter ce chiffre. Toute erreur de dimension lors de l’insertion provoquera une exception immédiate de PyMilvus.
Étape 3 : L’indexation. C’est l’étape la plus critique. Sans index, Milvus effectue un scan complet (brute-force), ce qui est catastrophique sur des millions de lignes. Utilisez l’index HNSW. Configurez le paramètre M (nombre de liens par nœud) et efConstruction. Un M élevé améliore la précision mais augmente l’empreinte RAM. Pour un usage standard, M=16 est un bon compromis.
Étape 4 : Recherche. Une fois l’index construit, la recherche devient extrêmement rapide. L’utilisation de la distance de Minkowski ou de la distance cosinus dépend de la normalisation de vos vecteurs. Si vos vecteurs ne sont pas normalisés à l’unité, la distance cosinus ne donnera pas les mêmes résultats qu’une simple distance euclidienne.
▶️ Exemple d’utilisation
Voici comment tester l’insertion et la recherche après avoir configuré le schéma.
import numpy as np
from pymilvus import Collection
# Simulation de données
vectors = np.random.random((10, 128)).astype('float32').tolist()
query_vec = np.random.random((1, 128)).astype('float32').tolist()
# Insertion (en supposant que la collection existe)
col = Collection("test_collection")
col.insert([vectors])
# Recherche
results = col.search(data=query_vect, anns_field="embedding", param={"metric_type":"L2"}, limit=3)
for hits in results:
for hit in hits:
print(f"ID: {hit.id}, Distance: {hit.distance}")
Sortie attendue :
ID: 4, Distance: 0.89234
ID: 12, Distance: 0.91231
ID: 7, Distance: 0.95432
🚀 Cas d’usage avancés
1. **Recherche d’images par similarité** : Utilisez un modèle ResNet pour extraire les vecteurs de vos images. Stockez-les dans une base de données vectorielle. La requête devient une simple comparaison de vecteurs extraits de la photo de l’utilisateur.
2. **Systèmes RAG (Retrieval Augmented Generation)** : Pour limiter les hallucinations des LLM, extrayez des segments de texte de votre documentation technique. Transformez-les en vecteurs. Lors d’une question, cherchez les segments les plus proches dans votre base de données vectorielle pour les injecter dans le prompt du modèle.
3. **Détection d’anomalies de logs** : Transformez des lignes de logs en séquences de vecteurs. Une distance trop élevée par rapport aux vecteurs de référence signale un comportement anormal dans votre cluster Kubernetes.
🐛 Erreurs courantes
⚠️ Dimension mismatch
Tentative d’insérer un vecteur de dimension 512 dans une collection définie pour 128.
col.insert([[1, 2, 3]]) # Si dim=128
col.insert([[1.0] * 128])
⚠️ Collection non chargée
Tentative de recherche sans avoir appelé la méthode load().
col.search(data=[vec], ...)
col.load(); col.search(data=[vec], ...)
⚠️ Index manquant
Recherche sur une collection sans index HNSW ou IVF.
col.search(data=[vec], ...)
col.create_index("embedding", {"index_type":"HNSW", "metric_type":"L2"}); col.search(...)
⚠️ Type de données incorrect
Utilisation de doubles au lieu de floats dans le vecteur.
vectors = [[1.0, 2.0]] # Python float est double precision
vectors = np.array([[1.0, 2.0]], dtype='float32').tolist()
✅ Bonnes pratiques
Pour maintenir une base de données vectorielle performante en production, suivez ces règles :
- Batching : N’insérez jamais les vecteurs un par un. Regroupez vos insertions par paquets de 500 à 1000 pour optimorer les IOPS sur MinIO.
- Partitionnement : Utilisez des partitions pour séparer les données par date ou par catégorie. Cela réduit l’espace de recherche pour l’algorithme ANN.
- Monitoring du Heap : Surveillez la consommation RAM de vos nœuds. Une base de données vectorielle est gourmande car l’index HNSW réside principalement en mémoire.
- Normalisation : Si vous utilisez la métrique Cosine, normalisez vos vecteurs à l’unité (L2 norm = 1) avant l’insertion. Cela accélère le calcul.
- Versioning du Schéma : Ne modifiez jamais la dimension d’un champ existant. Créez une nouvelle collection et migrez les données via un script de transformation.
- Milvus nécessite un orchestrateur comme Docker pour ses microservices.
- La dimension des vecteurs est strictement immuable après création.
- L'indexation HNSW est indispensable pour éviter le scan linéaire.
- Le chargement explicite (load) est requis avant toute recherche.
- Le choix de la métrique (L2 vs Cosine) dépend de la nature de vos données.
- Le batching des insertions est crucial pour la performance d'écriture.
- La gestion de la mémoire RAM est le principal point de défaillance.
- L'utilisation de float32 est le standard pour l'efficacité spatiale.
❓ Questions fréquentes
Est-ce que Milvus peut remplacer PostgreSQL pour du texte ?
Non. PostgreSQL est excellent pour les données structurées. Milvus est spécialisé uniquement pour les vecteurs haute dimension. Utilisez les deux en complément.
Quelle est la différence entre IVF et HNSW ?
IVF divise l’espace en clusters (plus rapide à construire, moins précis). HNSW utilise un graphe (plus rapide à chercher, plus gourmand en RAM).
Peut-on stocker des métadonnées textuelles dans Milvus ?
Oui, via des champs Scalar. Cependant, pour des recherches complexes mêlant texte et vecteurs, un moteur hybride est préférable.
Comment gérer la montée en charge ?
Milvus est conçu pour le scaling horizontal. Vous pouvez ajouter des QueryNodes supplémentaires pour augmenter le débit de recherche.
📚 Sur le même blog
🔗 Le même sujet sur nos autres blogs
📝 Conclusion
La mise en place d’une base de données vectorielle est une étape nécessaire pour tout projet de recherche sémantique moderne. Milvus offre les primitives nécessaires pour passer de l’expérimentation à la production massive. Pour approfondir la manipulation des structures de données en Perl, consultez la documentation Perl officielle. N’oubliez pas que la performance d’un index dépend toujours de la qualité de vos embeddings initiaux.
2 réflexions sur « Base de données vectorielle : déployer Milvus pour la recherche sémantique »