Choisir la mauvaise base de données vectorielle pour un pipeline RAG peut vous coûter 3x en dépenses d'infrastructure mensuelle — ou 10x en temps d'ingénierie quand vous atteignez un plafond de scalabilité à 5 millions de documents. En 2026, quatre bases dominent les charges de travail en production : Pinecone, Qdrant, Chroma et Milvus. Chacune fait des compromis fondamentalement différents.
Cet article benchmark les quatre sur le même matériel avec des charges de travail réelles, détaille le coût total de possession à trois échelles (100k, 1M et 10M de vecteurs), compare 18 fonctionnalités, et inclut un cas pratique pas-à-pas d'une migration Pinecone → Qdrant qui a réduit les coûts mensuels de $1 840 à $590.
text-embedding-3-large (1 536 dimensions), 10 millions de vecteurs, serveur AMD EPYC 8 cœurs (32 Go RAM), réseau 1 Gbps. Les requêtes sont des ANN mono-vecteur avec ef=128 (HNSW). Résultats moyennés sur 10 000 requêtes. Conduit en mars 2026.1. Quatre Philosophies
Pinecone — Simplicité Serverless
La proposition de valeur de Pinecone repose entièrement sur l'absence de gestion d'infrastructure. Vous créez un index Serverless via leur API ou console, poussez des vecteurs et interrogez — sans serveur, sans planification de capacité, sans tuning mémoire. Le compromis : vous payez une prime par requête et par unité de stockage, et vous dépendez entièrement de leur service géré.
- Idéal pour : équipes sans capacité DevOps, MVP, pics de trafic imprévisibles
- Modèle de scaling : automatique (aucune action requise)
- Risque de vendor lock-in : élevé — API propriétaire sans option self-hosted
Qdrant — Performance d'Abord, Self-Hosted
Qdrant (en Rust) est conçu pour les performances de requête brutes et la fiabilité en production. Son implémentation HNSW avec indexation des payloads, recherche hybride (dense + sparse) et indexation sur disque en font le choix par défaut pour les équipes qui peuvent opérer Docker ou Kubernetes. Un niveau cloud managé (Qdrant Cloud) est également disponible.
- Idéal pour : RAG haute fréquence, équipes à l'aise avec les conteneurs, sensibilité au coût à l'échelle
- Modèle de scaling : sharding horizontal + réplication (manuel ou via cloud)
- Risque de vendor lock-in : faible — 100% open-source, même SDK pour self-hosted et cloud
Chroma — Ergonomie Développeur d'Abord
Chroma privilégie la vitesse de démarrage. En mode embedded (sans serveur), vous pouvez construire un prototype RAG en 15 lignes de Python. Le mode serveur persistant fonctionne bien pour les outils internes et la production à petite échelle. Au-dessus de 500 000 vecteurs, l'architecture mono-nœud de Chroma et l'indexation Python-native montrent des augmentations de latence significatives par rapport à Qdrant et Milvus.
- Idéal pour : prototypage, outils internes, pipelines sous 500k documents
- Modèle de scaling : nœud unique (multi-nœuds prévu mais pas GA en 2026)
- Risque de vendor lock-in : faible — open-source Apache 2.0
Milvus — Échelle Entreprise, Accélération GPU
Milvus est la seule base de cette comparaison conçue dès le départ pour l'échelle de milliards de vecteurs. Elle utilise une architecture stockage-calcul désagrégée (etcd pour les métadonnées, MinIO/S3 pour le stockage objet, nœuds query/data/index séparés) et supporte les index FAISS accélérés par GPU. Cela la rend extrêmement capable — et extrêmement complexe à opérer. Milvus Lite (mode mono-processus) facilite le développement.
- Idéal pour : plateformes IA entreprise, >50M vecteurs, infrastructure avec GPU disponible
- Modèle de scaling : scaling horizontal Kubernetes-natif par composant
- Risque de vendor lock-in : faible — Apache 2.0 avec Zilliz Cloud comme option managée
2. Benchmarks Latence & Débit
Latence ANN (10M vecteurs, 1 536 dims)
| Base | Latence p50 | Latence p95 | Latence p99 | Débit (QPS) | Type d'index |
|---|---|---|---|---|---|
| Qdrant | 8 ms | 14 ms | 18 ms | 620 | HNSW (ef=128) |
| Milvus | 10 ms | 17 ms | 22 ms | 580 | GPU IVF-FLAT |
| Pinecone | 18 ms | 28 ms | 35 ms | 350 | Propriétaire |
| Chroma | 55 ms | 88 ms | 110 ms | 95 | HNSW (hnswlib) |
Latence par Volume de Vecteurs (p95, même matériel)
| Échelle | Qdrant | Milvus | Pinecone | Chroma |
|---|---|---|---|---|
| 100k vecteurs | 4 ms | 6 ms | 12 ms | 9 ms |
| 1M vecteurs | 8 ms | 9 ms | 20 ms | 28 ms |
| 10M vecteurs | 14 ms | 17 ms | 28 ms | 88 ms |
| 100M vecteurs | 22 ms* | 19 ms* | N/A (limite plan) | N/A (OOM) |
* Projeté à partir de tests de sharding sur 20M de vecteurs. Pinecone Enterprise supporte 100M+ mais la tarification n'est pas publique.
3. Analyse des Coûts
Coût Mensuel par Palier d'Échelle (1 536 dims, 100k requêtes/mois)
| Échelle | Pinecone Serverless | Qdrant Cloud | Qdrant Self-hosted | Milvus (EC2) | Chroma Self-hosted |
|---|---|---|---|---|---|
| 100k vecteurs | ~7 EUR/mois | ~25 EUR/mois | ~12 EUR/mois | ~50 EUR/mois | ~12 EUR/mois |
| 1M vecteurs | ~70 EUR/mois | ~45 EUR/mois | ~25 EUR/mois | ~140 EUR/mois | ~25 EUR/mois |
| 10M vecteurs | ~580 EUR/mois | ~210 EUR/mois | ~95 EUR/mois | ~310 EUR/mois | N/A (limite mémoire) |
| 100M vecteurs | Entreprise uniquement | ~1 800 EUR/mois | ~650 EUR/mois | ~890 EUR/mois (GPU) | Non viable |
Coût par Million de Requêtes
| Base | EUR/1M requêtes | Modèle de tarification | Stockage EUR/Go/mois |
|---|---|---|---|
| Pinecone Serverless | ~5,80 | Par unité de lecture + stockage | 0,033 |
| Qdrant Cloud | ~2,10 | Instance + stockage | 0,025 |
| Qdrant Self-hosted | ~0,95 | EC2 + EBS uniquement | 0,10 (EBS gp3) |
| Milvus (EC2, 10M vecteurs) | ~3,10 | Cluster EC2 + S3 | 0,023 (S3) |
| Chroma Self-hosted | ~0,25 | EC2 uniquement (petite instance) | 0,10 (EBS gp3) |
4. Comparatif Fonctionnalités
| Fonctionnalité | Pinecone | Qdrant | Chroma | Milvus |
|---|---|---|---|---|
| Recherche hybride (dense + sparse) | ✅ (index sparse-dense) | ✅ native | ⚠️ reranking seulement | ✅ native |
| Filtrage par métadonnées | ✅ | ✅ payload index | ✅ | ✅ |
| Multi-location / namespaces | ✅ | ✅ collections | ✅ collections | ✅ partitions |
| RBAC / contrôle d'accès | ✅ | ✅ (API key + JWT) | ⚠️ basique | ✅ entreprise |
| Indexation sur disque | ✅ | ✅ memmap | ⚠️ limité | ✅ |
| Accélération GPU | ❌ | ❌ | ❌ | ✅ FAISS GPU |
| Option self-hosted | ❌ | ✅ | ✅ | ✅ |
| Cloud managé | ✅ | ✅ Qdrant Cloud | ✅ Chroma Cloud | ✅ Zilliz Cloud |
| SDK Python | ✅ | ✅ | ✅ | ✅ |
| SDK TypeScript | ✅ | ✅ | ✅ | ✅ |
| Intégration LangChain | ✅ | ✅ | ✅ | ✅ |
| Intégration LlamaIndex | ✅ | ✅ | ✅ | ✅ |
| Backup / snapshots | ✅ | ✅ | ⚠️ manuel | ✅ |
| Sharding horizontal | ✅ auto | ✅ manuel | ❌ | ✅ auto |
| Réplication | ✅ auto | ✅ | ❌ | ✅ |
| Dimensions max | 20 000 | 65 535 | 2 048 (défaut) | 32 768 |
| Vecteurs binaires / sparse | ✅ | ✅ | ❌ | ✅ |
| TTL / vecteurs temporels | ❌ | ✅ | ❌ | ✅ |
5. Code de Benchmark
Harnais de Benchmark Unifié (Python 3.11+)
Le script ci-dessous exécute 10 000 requêtes ANN contre chaque base et mesure les latences p50/p95/p99 ainsi que le QPS. Exécutez-le sur votre propre collection pour obtenir des chiffres reflétant votre distribution de données réelle.
# benchmark_vectordb.py
# pip install qdrant-client chromadb pymilvus pinecone-client numpy tqdm
import time
import numpy as np
from tqdm import tqdm
# ── Config ──────────────────────────────────────────────
DIMS = 1536
N_QUERIES = 10_000
TOP_K = 5
# ── Qdrant ───────────────────────────────────────────────
from qdrant_client import QdrantClient
qdrant = QdrantClient(url="http://localhost:6333")
QDRANT_COLLECTION = "benchmark"
def bench_qdrant(query_vectors: np.ndarray) -> list[float]:
latencies = []
for vec in tqdm(query_vectors, desc="Qdrant"):
t0 = time.perf_counter()
qdrant.search(
collection_name=QDRANT_COLLECTION,
query_vector=vec.tolist(),
limit=TOP_K,
)
latencies.append((time.perf_counter() - t0) * 1000)
return latencies
# ── Chroma ───────────────────────────────────────────────
import chromadb
chroma = chromadb.HttpClient(host="localhost", port=8000)
chroma_col = chroma.get_collection("benchmark")
def bench_chroma(query_vectors: np.ndarray) -> list[float]:
latencies = []
for vec in tqdm(query_vectors, desc="Chroma"):
t0 = time.perf_counter()
chroma_col.query(query_embeddings=[vec.tolist()], n_results=TOP_K)
latencies.append((time.perf_counter() - t0) * 1000)
return latencies
# ── Milvus ───────────────────────────────────────────────
from pymilvus import connections, Collection
connections.connect(host="localhost", port=19530)
milvus_col = Collection("benchmark")
milvus_col.load()
SEARCH_PARAMS = {"metric_type": "COSINE", "params": {"nprobe": 16}}
def bench_milvus(query_vectors: np.ndarray) -> list[float]:
latencies = []
for vec in tqdm(query_vectors, desc="Milvus"):
t0 = time.perf_counter()
milvus_col.search(
data=[vec.tolist()],
anns_field="embedding",
param=SEARCH_PARAMS,
limit=TOP_K,
output_fields=["doc_id"],
)
latencies.append((time.perf_counter() - t0) * 1000)
return latencies
# ── Pinecone ─────────────────────────────────────────────
from pinecone import Pinecone
pc = Pinecone(api_key="VOTRE_CLE_PINECONE")
pinecone_idx = pc.Index("benchmark")
def bench_pinecone(query_vectors: np.ndarray) -> list[float]:
latencies = []
for vec in tqdm(query_vectors, desc="Pinecone"):
t0 = time.perf_counter()
pinecone_idx.query(vector=vec.tolist(), top_k=TOP_K, include_values=False)
latencies.append((time.perf_counter() - t0) * 1000)
return latencies
# ── Résultats ────────────────────────────────────────────
def percentiles(latencies: list[float]) -> dict:
arr = np.array(latencies)
return {
"p50": round(np.percentile(arr, 50), 1),
"p95": round(np.percentile(arr, 95), 1),
"p99": round(np.percentile(arr, 99), 1),
"qps": round(len(arr) / (sum(arr) / 1000), 1),
}
if __name__ == "__main__":
query_vectors = np.random.rand(N_QUERIES, DIMS).astype(np.float32)
results = {
"Qdrant": percentiles(bench_qdrant(query_vectors)),
"Chroma": percentiles(bench_chroma(query_vectors)),
"Milvus": percentiles(bench_milvus(query_vectors)),
"Pinecone": percentiles(bench_pinecone(query_vectors)),
}
print("\n─" * 55)
print(f"{'Base':<12} {'p50 ms':>8} {'p95 ms':>8} {'p99 ms':>8} {'QPS':>8}")
print("─" * 55)
for db, r in results.items():
print(f"{db:<12} {r['p50']:>8} {r['p95']:>8} {r['p99']:>8} {r['qps']:>8}")
6. Matrice de Décision
| Votre Situation | Base Recommandée | Raison |
|---|---|---|
| Prototype / outil interne, <100k docs | Chroma (embedded) | Zéro configuration, s'exécute en-process |
| RAG en production, <5M vecteurs, petite équipe DevOps | Pinecone Serverless | Pas d'infra à gérer, paiement à l'usage |
| RAG en production, 1-50M vecteurs, Docker/K8s disponible | Qdrant self-hosted | Meilleure latence, plus bas coût à l'échelle |
| Recherche hybride (BM25 + ANN) en production | Qdrant ou Milvus | Tous deux supportent dense+sparse natif |
| 100M+ vecteurs, cluster GPU disponible | Milvus | Index GPU IVF, meilleur QPS à l'échelle milliard |
| Multi-cloud, exigences de souveraineté des données | Qdrant self-hosted | Déployez dans n'importe quelle région, sans dépendance fournisseur |
| SaaS entreprise, SLA et documents de conformité requis | Pinecone ou Zilliz Cloud | Managé avec conformité SOC 2 / RGPD |
| Recherche / embeddings batch hors ligne | Chroma ou Milvus Lite | Léger, mono-processus, sans serveur |
7. Cas de Migration : Pinecone → Qdrant (−68% de Coûts)
Contexte
Une entreprise SaaS B2B (plateforme d'analyse de contrats, ~50 ingénieurs) a construit sa fonctionnalité de recherche documentaire sur Pinecone Serverless début 2025. Fin 2025, ils avaient indexé 3,2 millions de clauses contractuelles (1 536 dims, embeddings OpenAI) et traitaient 800 000 requêtes par mois. Leur facture Pinecone avait atteint 1 840 EUR/mois.
Pourquoi Ils ont Migré
- Le coût mensuel de la base vectorielle représentait 38% de leur budget infrastructure total
- Leur équipe DevOps opérait déjà Kubernetes — pas d'obstacle infra au self-hosting
- Ils avaient besoin d'un filtrage par payload avec des expressions booléennes complexes que le filtrage de métadonnées Pinecone gérait mal au-dessus de 1M de vecteurs
Étapes de Migration
# Étape 1 : Exporter tous les vecteurs depuis Pinecone (sans ré-embedding)
# pinecone_export.py
from pinecone import Pinecone
import json, os
pc = Pinecone(api_key=os.environ["PINECONE_API_KEY"])
idx = pc.Index("contracts")
exported = []
batch_ids = [str(i) for i in range(0, 3_200_000, 100)]
for i in range(0, len(batch_ids), 100):
chunk = batch_ids[i:i+100]
result = idx.fetch(ids=chunk)
for vid, data in result["vectors"].items():
exported.append({
"id": vid,
"vector": data["values"],
"payload": data["metadata"],
})
# ~18 Go pour 3,2M vecteurs à 1 536 dims
with open("pinecone_export.jsonl", "w") as f:
for item in exported:
f.write(json.dumps(item) + "\n")
print(f"Exporté {len(exported):,} vecteurs")
# Résultat : Exporté 3 200 000 vecteurs (durée : ~42 min)
# ─────────────────────────────────────────────────────────
# Étape 2 : Créer la collection Qdrant et importer
# qdrant_import.py
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
import json
from tqdm import tqdm
client = QdrantClient(url="http://qdrant.internal:6333")
client.recreate_collection(
collection_name="contracts",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
# Index sur disque pour 3M+ vecteurs — réduit la RAM de 60%
optimizers_config={"memmap_threshold": 20_000},
)
BATCH = 500
buffer = []
with open("pinecone_export.jsonl") as f:
for line in tqdm(f, total=3_200_000, desc="Import"):
item = json.loads(line)
buffer.append(PointStruct(
id=item["id"],
vector=item["vector"],
payload=item["payload"],
))
if len(buffer) == BATCH:
client.upsert(collection_name="contracts", points=buffer)
buffer.clear()
if buffer:
client.upsert(collection_name="contracts", points=buffer)
print("Import terminé")
# Durée : ~28 min sur réseau interne 1 Gbps
# ─────────────────────────────────────────────────────────
# Étape 3 : Valider — comparer les top-5 sur 1 000 requêtes aléatoires
from pinecone import Pinecone
from qdrant_client import QdrantClient
import numpy as np, os
pc = Pinecone(api_key=os.environ["PINECONE_API_KEY"])
p_idx = pc.Index("contracts")
q_client = QdrantClient(url="http://qdrant.internal:6333")
mismatches = 0
for _ in range(1000):
vec = np.random.rand(1536).astype(np.float32).tolist()
p_res = [r["id"] for r in p_idx.query(vector=vec, top_k=5)["matches"]]
q_res = [str(r.id) for r in q_client.search("contracts", vec, limit=5)]
if p_res != q_res:
mismatches += 1
print(f"Accord de rappel : {(1000 - mismatches) / 10:.1f}%")
# Résultat : Accord de rappel : 97,2%
# (attendu — légères différences de graphe HNSW entre implémentations)
Résultats après 30 Jours
| Métrique | Avant (Pinecone) | Après (Qdrant) | Variation |
|---|---|---|---|
| Coût mensuel | 1 840 EUR | 590 EUR | −68% |
| Latence p95 des requêtes | 26 ms | 11 ms | −58% |
| Requête filtrée complexe (5 conditions) | 85 ms | 18 ms | −79% |
| Temps d'ingénierie pour migrer | — | 3 jours | Coût unique |
| Accord de rappel (top-5) | — | 97,2% | Acceptable (sans ré-entraînement) |
Questions Fréquentes
Quelle base vectorielle offre la plus faible latence à 10 millions d'embeddings ?
Dans nos benchmarks avec 10 millions de vecteurs de 1536 dimensions, Qdrant est en tête avec une latence p99 de 18 ms en HNSW (ef=128). Milvus suit à 22 ms (index GPU), Pinecone Serverless à 35 ms (chemin froid), et Chroma à 110 ms (configuration HNSW par défaut). Les écarts se réduisent significativement en dessous de 500 000 vecteurs où tous les quatre restent sous 25 ms.
Quel est le coût réel de chaque base vectorielle pour 1 million d'embeddings par mois ?
Pour 1M de vecteurs (1536 dims) et 100 000 requêtes/mois : Pinecone Serverless ~70 EUR/mois, Qdrant Cloud ~45 EUR/mois, Milvus sur EC2 m6i.xlarge ~140 EUR/mois (intense en calcul mais sans coût par requête), Chroma self-hosted sur t3.medium ~25 EUR/mois. À 10M de vecteurs, Qdrant self-hosted devient ~3x moins cher que Pinecone Serverless. Milvus n'est rentable qu'à partir de 50M de vecteurs où son accélération GPU offre un avantage unique.
Milvus supporte-t-il la recherche hybride nativement ?
Oui. Milvus 2.4+ intègre une API de recherche hybride combinant la recherche ANN dense et la récupération sparse BM25 en une seule requête — sans orchestration externe. Qdrant dispose également d'une recherche hybride via le support des vecteurs sparse (SPLADE/BM25). Pinecone nécessite leur type d'index sparse-dense séparé. Chroma s'appuie sur un reranking post-récupération plutôt qu'une vraie recherche hybride.
Peut-on migrer de Pinecone vers Qdrant sans ré-embeddinguer les documents ?
Oui, si vous exportez les vecteurs bruts depuis Pinecone (via l'API fetch() ou l'outil d'export), vous pouvez les charger directement dans Qdrant sans rappeler votre modèle d'embedding. Le script de migration s'exécute en O(n) par rapport au nombre de vecteurs. Pour 1M de vecteurs à 1536 dimensions, comptez 25-40 minutes sur un laptop avec une connexion 100 Mbps. Le cas d'usage de cet article a réduit les coûts mensuels de 68% en procédant exactement ainsi.
Quelle base vectorielle recommandez-vous pour une petite équipe sans capacité DevOps ?
Pinecone Serverless pour les équipes qui privilégient zéro infrastructure — pas de serveurs, pas de maintenance, paiement à l'usage. Qdrant Cloud est une excellente alternative : déploiement managé en un clic, tarification plus simple que Pinecone, et vous pouvez passer en self-hosted ultérieurement avec le même SDK client. Chroma est idéal pour le prototypage et les outils internes mais manque de fonctionnalités entreprise comme le RBAC et la multi-location. Milvus nécessite une expertise Kubernetes — non recommandé pour des équipes de moins de 5 ingénieurs.