Sommaire
Tout projet RAG commence par la même liste de courses : « il nous faut une base vectorielle ». Trois noms dominent la short-list sur Kubernetes — pgvector (extension Postgres), Milvus et Weaviate. Ils ont l’air interchangeables sur les pages marketing et se comportent très différemment dès qu’on les pousse à quelques centaines de millions d’embeddings.
Cet article les compare honnêtement sur VKS : qualité d’index et compromis de recall, surface ops (backups, scaling, upgrades), et celui qui colle vraiment à la maturité de ton équipe. Spoiler — pour beaucoup de cas RAG internes, la réponse n’est pas celle que le consensus IA de Twitter promeut.
TL;DR
- pgvector est « assez bon » jusqu’à quelques dizaines de millions de vecteurs — au-delà, le coût de build d’index devient pénible.
- Milvus passe à l’échelle mais impose un control plane que ton équipe SRE doit apprendre à aimer.
- Weaviate troque la vitesse brute contre une surface ops plus accessible et un écosystème de modules intégrés.
Ce qu’une « base vectorielle » doit vraiment faire
Enlève le marketing et la fiche de poste tient en quatre lignes. C’est justement parce qu’elle est courte que trois produits qui font la même chose finissent aussi différents à exploiter.
La recherche approximative de plus proches voisins. Un k-NN exact, c’est un scan linéaire : tu calcules la distance sur tous les vecteurs et tu gardes les k meilleurs. Correct, parallélisable, et intenable au-delà de quelques centaines de milliers de lignes. Un index ANN échange un peu de recall contre beaucoup moins de calculs de distance. Deux familles dominent. Les index de graphe — HNSW, que tu croiseras partout — construisent un graphe navigable au-dessus des vecteurs et répondent par une marche gloutonne ; le recall se règle à la requête (ef_search), la qualité de construction à l’écriture (m, ef_construction). Les index à fichier inversé — IVF, souvent couplé à de la quantification produit ou scalaire — découpent l’espace en cellules et n’en sondent qu’une poignée ; ils se construisent plus vite, consomment beaucoup moins de mémoire, et demandent nettement plus de réglage pour atteindre le même recall.
Le triangle recall / latence / coût de build. Tu en choisis deux. Tu montes ef_search : le recall s’améliore, la p99 grimpe. Tu montes m et ef_construction : recall et latence s’améliorent, mais le build s’allonge et le chemin d’écriture s’alourdit. Tu quantifies : tu gagnes de la mémoire et du temps de build, tu perds du recall. HNSW s’est imposé par défaut parce qu’il est indulgent sur les deux premiers axes et coûteux sur le troisième — ce qui va très bien jusqu’au jour où il faut tout reconstruire.
Le filtrage en même temps que la recherche vectorielle. C’est là que les systèmes RAG cassent en production, et quasiment aucun benchmark public ne le mesure. Une vraie requête n’est jamais « les dix vecteurs les plus proches » ; c’est « les dix vecteurs les plus proches que cet utilisateur a le droit de lire, sur des documents non périmés, dans la bonne langue ». Le post-filtrage demande k résultats à l’index puis jette ceux qui échouent au prédicat — tu en voulais dix, tu en récupères trois. Le pré-filtrage restreint la recherche au sous-ensemble autorisé, ce qu’une marche dans un graphe supporte mal quand la sélectivité est brutale : les arêtes mènent majoritairement vers des nœuds exclus et la traversée dégénère. Chaque moteur bricole sa parade — traversée filtrée par bitmap, ou repli en force brute sous un seuil de cardinalité. Teste ton tenant le plus défavorable, pas le tenant moyen.
Les mises à jour et les suppressions. Les index de graphe adorent l’ajout et détestent la suppression. Un delete est généralement un tombstone : le vecteur reste dans le graphe, il est filtré des résultats, et il continue de te coûter de la mémoire et des sauts de traversée jusqu’à une compaction ou une reconstruction. Un corpus qui bouge beaucoup se dégrade donc sur un calendrier que personne n’a planifié. Et le plus gros déclencheur de rebuild n’est même pas la donnée : c’est le changement de modèle d’embedding. Nouveau modèle, nouvel espace vectoriel, tous les vecteurs stockés deviennent invalides. Traite le ré-embedding complet comme une opération de routine que tu joueras plusieurs fois par an.
L'index est un artefact dérivé
Garde le texte des chunks, leurs métadonnées et l’ACL associée dans un stockage durable que tu maîtrises — object storage, ou de simples tables Postgres. L’index vectoriel doit être reconstructible à zéro par un batch. Prends cette décision au jour un et le choix du moteur cesse d’être une porte à sens unique : migrer devient un ré-indexage, pas un projet de migration.
pgvector, Milvus, Weaviate côte à côte sur VKS
Les vraies différences ne sont pas dans les maths de l’ANN : les trois embarquent HNSW et les trois te rendront un top-k en quelques millisecondes sur un working set chaud. Les différences sont morphologiques : combien de pièces mobiles atterrissent dans ton cluster, et qui se fait réveiller quand l’une d’elles s’arrête.
pgvector hérite de tout ce que tu exploites déjà. C’est une extension, pas un produit. Sur VKS tu déploies Postgres avec l’opérateur sur lequel tu as déjà un avis — CloudNativePG, Crunchy, Zalando — et tu obtiens un primaire plus des réplicas en StatefulSet, avec des PVC sur ta storage class vSAN. Ensuite tu fais CREATE EXTENSION vector et la colonne vectorielle n’est qu’une colonne de plus. Tout ton runbook continue de s’appliquer : PITR par archivage WAL, pg_stat_statements, pooling de connexions, ta supervision, ta politique de sauvegarde, ton astreinte. Et le filtrage n’est pas une fonctionnalité dont tu espères que l’éditeur l’a bien implémentée — c’est du SQL, avec de vraies jointures sur de vraies tables :
SELECT c.id, c.doc_id, c.text
FROM chunks c
JOIN doc_acl a ON a.doc_id = c.doc_id
WHERE a.group_id = ANY($2)
AND c.superseded_at IS NULL
ORDER BY c.embedding <=> $1
LIMIT 10;
Le planner décide s’il passe par l’index HNSW ou s’il filtre d’abord puis scanne — et tu peux inspecter cette décision avec EXPLAIN ANALYZE, ce que très peu de moteurs vectoriels t’offrent. Les limites sont tout aussi franches : le build d’index est gourmand en mémoire et largement borné par maintenance_work_mem, l’index veut vivre en page cache, et il n’y a pas de sharding natif de l’index vectoriel entre plusieurs nœuds. Tu scales verticalement, puis tu partitionnes par tenant ou par collection au niveau applicatif.
Milvus amène son propre control plane. C’est un vrai système distribué, et il l’assume : des proxies en frontal, un jeu de coordinateurs, et des pools distincts de query nodes, data nodes et index nodes. En dessous, trois dépendances externes — etcd pour les métadonnées, un object storage compatible S3 pour les segments, et un log broker pour le chemin d’écriture. Autrement dit, avant de stocker ton premier vecteur, tu as ajouté trois systèmes stateful à la plateforme, chacun avec son rythme d’upgrade, ses modes de panne et ses dashboards. Ce que tu achètes en échange est réel : build d’index déporté sur des nœuds dédiés, stockage découplé du compute, query nodes scalés indépendamment, et des collections trop grosses pour tenir sur une seule machine. Le chart Helm et l’opérateur rendent l’installation facile — c’est le piège : installer Milvus prend une matinée, l’exploiter est une discipline.
Weaviate se place entre les deux. Un binaire Go unique, en cluster, avec des shards qui portent chacun leur index HNSW et leur stockage LSM, et un consensus interne pour le schéma et l’appartenance au cluster plutôt qu’un etcd externe. Pas de broker, pas d’object storage requis sur le chemin chaud. Tu exploites un StatefulSet, tu ajoutes des réplicas, tu obtiens du sharding horizontal sans adopter une ménagerie distribuée. En contrepartie tu acceptes un écosystème sans commune mesure avec celui de Postgres, des comportements de modules qui bougent d’une version mineure à l’autre, et des caractéristiques de resharding que tu ferais bien de vérifier sur tes propres données avant d’en dépendre.
| pgvector | Milvus | Weaviate | |
|---|---|---|---|
| Unité de déploiement | StatefulSet Postgres via opérateur | Plusieurs pools de composants + opérateur | StatefulSet en cluster, un binaire |
| Dépendances externes | aucune au-delà de Postgres | etcd, object storage, log broker | aucune requise |
| Modèle de filtrage | SQL complet, jointures, planner inspectable | recherche filtrée native, décidée par le moteur | recherche filtrée native, décidée par le moteur |
| Axe de scale-out | vertical, puis partitionnement applicatif | horizontal, par rôle de composant | shards horizontaux par classe |
| Chemin de backup | celui que tu exploites déjà pour Postgres | snapshot cohérent multi-systèmes | API de backup native vers object storage |
| Compétences requises | DBA Postgres, que tu as sans doute | astreinte sur du distribué | Kubernetes plus spécificités éditeur |
Backups, scaling, upgrades : la partie ennuyeuse
Personne ne choisit un datastore sur sa procédure de restauration, et tout le monde le regrette. C’est pourtant ici que la décision se joue vraiment.
Backups. Avec pgvector il n’y a pas de nouvelle histoire de sauvegarde, et c’est tout l’intérêt : base backup plus archivage WAL vers l’object storage te donne du point-in-time recovery, et l’exercice de restauration que ton DBA répète déjà couvre gratuitement la donnée vectorielle. Milvus exige une photo cohérente entre les métadonnées etcd, les segments en object storage et les écritures en vol : un snapshot naïf des PVC des coordinateurs n’est pas une sauvegarde. Weaviate expose une API de backup qui pousse vers l’object storage par classe, ce qui est propre, mais la sémantique de restauration et le support de restauration inter-versions font partie des détails à vérifier dans ta version cible plutôt qu’à supposer.
Scaling. Sois précis sur la ressource qui s’épuise réellement. Pour de l’ANN, c’est presque toujours la mémoire, pas le CPU ni les IOPS : un index qui tient en RAM est rapide, un index qui déborde sur disque tombe d’une falaise. pgvector scale en donnant plus de RAM au nœud et en ajoutant des réplicas de lecture pour répartir les requêtes ; au-delà, tu partitionnes par tenant. Milvus scale en ajoutant des query nodes, mais une collection chargée est résidente en mémoire de query node : « scaler horizontalement » veut dire « acheter assez de RAM agrégée », le cluster te permet juste de l’étaler. Weaviate scale en ajoutant des shards, avec cette réserve que changer le nombre de shards d’une classe existante est exactement l’opération que tu veux avoir testée avant d’en avoir besoin.
Upgrades. Une montée de version mineure Postgres, c’est un rolling restart que ton opérateur gère ; une montée majeure est un événement planifié, pg_upgrade ou réplication logique, et tu dois lire les notes de version de pgvector pour repérer les changements de format d’index qui imposent un REINDEX — reconstruire un gros index HNSW se compte en heures, pas en minutes, donc planifie-le comme une migration. Les upgrades Milvus sont multi-composants et ordonnés, et tu portes en plus les cycles d’upgrade d’etcd et du broker. Weaviate se met à jour en rolling StatefulSet, avec les migrations de format d’index signalées dans les notes de version.
Joue la restauration avant d'indexer quoi que ce soit
La panne réaliste n’est pas le moteur qui tombe : c’est un job d’ingestion défectueux qui écrit trois jours d’embeddings pourris avant que quelqu’un remarque que les réponses se sont dégradées. Ton recours, c’est une restauration à un instant T, ou un ré-indexage complet depuis la source. Chronomètre les deux, sur un volume de production, avant que le corpus ne grossisse. Si le ré-indexage complet prend huit heures, c’est ça ton vrai RPO — et c’est parfaitement acceptable tant que tu le sais.
Les détails plateforme qui mordent sur VKS. Mets les requests mémoire égales aux limits sur tout pod qui porte un index, pour que le kubelet ne puisse pas évincer un pod en plein rebuild. Ajoute un PodDisruptionBudget, sinon un drain de nœud pendant un upgrade de cycle de vie VKS emportera ton quorum avec lui. Étale les réplicas sur plusieurs hôtes ESXi avec de l’anti-affinité. Et exporte dès le premier jour les métriques qui comptent — durée de build d’index, recall sur un jeu d’évaluation figé, latence p99 de recherche, taille résidente de l’index — vers ta stack Prometheus et Grafana, parce qu’on ne défend pas une migration sans baseline mesurée.
Par laquelle commencer
Commence par pgvector. C’est la réponse démodée, et elle est juste la plupart du temps.
L’argument est arithmétique avant d’être idéologique. Un corpus interne typique — un intranet, une documentation produit, quelques années de tickets de support — atterrit entre un et dix millions de chunks une fois découpé raisonnablement :
un chunk ≈ 300–500 tokens de texte source
un vecteur = 1024 dims × 4 octets ≈ 4 Kio
1 M de vecteurs ≈ 4 Gio de float32 brut
listes de voisins HNSW ≈ 2 × m × 4 octets (m = 16) ≈ 128 o par vecteur
5 M de vecteurs ≈ 20 Gio résidents, index compris
À cette taille, l’observation honnête n’est pas de savoir quel moteur gagne en requêtes par seconde. C’est que l’intégralité du working set tient dans la RAM d’un seul nœud correctement dimensionné, avec de la marge — et un système distribué dont la donnée tient sur une machine est un système distribué que tu paies sans t’en servir. Diviser le volume par deux est d’ailleurs plus simple que changer de moteur : réduire la dimension d’embedding, ou stocker en demi-précision quand ton moteur le permet, déplace le chiffre mémoire bien plus que n’importe quel réglage d’index.
Pendant ce temps, ce qui détermine réellement la qualité de ton RAG vit entièrement hors du vector store : la stratégie de chunking, les métadonnées que tu attaches, la présence ou non d’un re-ranking, et le fait d’avoir un jeu d’évaluation tout court. Chaque heure passée à exploiter un control plane dont tu n’as pas besoin est une heure non passée sur ce que les utilisateurs ressentent — un arbitrage à garder en tête quand tu arriveras à la mise en production du RAG.
Bouge quand une limite mesurée t’y oblige, et nomme la limite. Les déclencheurs raisonnables, par ordre de fréquence approximatif :
Le working set ne tient plus. L’index dépasse la RAM que tu peux raisonnablement donner à un nœud, et la latence est tombée de la falaise du débordement disque.
Le temps de rebuild dépasse la fenêtre. Un ré-indexage complet dure plus longtemps que l’intervalle auquel tu dois changer de modèle d’embedding ou retraiter le corpus.
L’écriture entre en collision avec la lecture. L’ingestion continue dégrade la latence de requête au-delà de ton SLO, et séparer le build du serve réglerait le problème.
L’isolation à l’échelle. Des centaines de tenants avec chacun sa collection, son cycle de vie et son quota — une forme que Milvus modélise nativement et Postgres non.
L’accélération matérielle. Tu as besoin de build ou de recherche assistés par GPU, ce qui n’a de sens que si le GPU est déjà là pour l’inférence.
Note ce qui ne figure pas dans cette liste : « un benchmark sur le dataset de quelqu’un d’autre ». Les benchmarks ANN publics tournent sur des corpus propres, statiques et non filtrés — précisément la seule charge que tu n’as pas.
Et si le déclencheur se produit vraiment, la migration ne fait mal que si tu as sauté le conseil du début. Avec le texte des chunks, les métadonnées et les ACL dans un stockage que tu maîtrises, passer à Milvus ou Weaviate est un batch qui lit la source de vérité et écrit dans un nouvel index, exécuté en parallèle de l’ancien jusqu’à ce qu’une comparaison sur ton jeu d’évaluation dise que le nouveau est au moins aussi bon. C’est une semaine de travail, pas un trimestre — et savoir que ça coûte une semaine est exactement ce qui rend légitime de commencer simple. Le même raisonnement vaut un étage plus bas, pour l’architecture Private AI elle-même : adopte les couches que tu sais exploiter, diffère les autres.
Conclusion
Trois moteurs, trois personnalités opérationnelles, et une décision qui compte plus que le choix entre eux : est-ce que ton index vectoriel est une base de données précieuse, ou un artefact dérivé jetable. Rends-le jetable et tout le reste redevient réversible.
Le filtrage est le vrai test
Le top-k sur corpus propre est un problème résolu. Le top-k sous ACL, règles de fraîcheur et frontières de tenant, c’est là que les moteurs divergent — mesure ça, sur ton tenant le plus défavorable.
La surface ops prime sur les QPS
pgvector hérite de ton runbook Postgres ; Weaviate ajoute un binaire en cluster ; Milvus ajoute etcd, un object storage et un broker. Choisis la forme que ton astreinte sait porter.
Commence petit, migre sur preuve
La plupart des corpus internes tiennent dans la RAM d’un nœud. Démarre sur pgvector, instrumente recall et temps de build, et ne bouge que quand un chiffre mesuré l’impose.
Reçois le prochain par email
Les nouveaux articles et séries, envoyés à leur publication. Aucun autre courrier.



