Comparez les bases vectorielles Pinecone, Qdrant, Weaviate et pgvector pour vos projets RAG : self-hosting, filtrage, recherche hybride, scaling, couts et arbre de decision.
Pourquoi une base vectorielle
Une base de donnees vectorielle stocke des embeddings — des representations numeriques du sens d'un texte, d'une image ou d'un son — et permet de retrouver les plus similaires a une requete. C'est le socle de la recherche semantique et de tout systeme RAG.
Contrairement a une base SQL classique qui cherche des correspondances exactes, une base vectorielle calcule la distance entre vecteurs (cosinus, produit scalaire, euclidienne) et renvoie les k plus proches voisins. L'enjeu : faire cela vite, sur des millions de vecteurs, avec du filtrage par metadonnees.
Criteres de choix
- Managed (zero ops) ou self-hosted (controle total) ?
- Volume : milliers, millions ou milliards de vecteurs ?
- Filtrage par metadonnees complexe necessaire ?
- Recherche hybride (vectoriel + mots-cles) requise ?
- Budget : cout par requete vs cout d'infrastructure ?
- Stack existante (deja PostgreSQL ? deja Kubernetes ?)
Pinecone : le managed sans ops
Pinecone est une base vectorielle entierement managee. Vous ne gerez ni serveur, ni index, ni scaling : vous appelez une API. C'est le choix par defaut quand vous voulez aller vite sans equipe ops.
// pinecone-demo.js — upsert et recherche
import { Pinecone } from '@pinecone-database/pinecone';
const pc = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const index = pc.index('articles');
// Inserer des vecteurs avec metadonnees
await index.upsert([
{
id: 'doc-1',
values: embedding, // tableau de floats (ex: 1536 dim)
metadata: { title: 'RAG explique', category: 'ai', year: 2026 },
},
]);
// Rechercher les 5 plus proches, filtres par metadonnees
const results = await index.query({
vector: queryEmbedding,
topK: 5,
includeMetadata: true,
filter: { category: { $eq: 'ai' } }, // filtrage cote serveur
});
results.matches.forEach((m) => {
console.log(`${m.score.toFixed(3)} — ${m.metadata.title}`);
});
Qdrant : open source et performant
Qdrant est ecrit en Rust, open source, et offre l'un des meilleurs rapports performance / simplicite. On peut le lancer en local via Docker en une commande, ou utiliser leur cloud manage.
# Lancer Qdrant en local via Docker
docker run -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
qdrant/qdrant
// qdrant-demo.js — creation de collection et recherche
import { QdrantClient } from '@qdrant/js-client-rest';
const client = new QdrantClient({ url: 'http://localhost:6333' });
// Creer une collection avec la dimension et la metrique
await client.createCollection('articles', {
vectors: { size: 1536, distance: 'Cosine' },
});
// Inserer des points (vecteur + payload de metadonnees)
await client.upsert('articles', {
points: [{
id: 1,
vector: embedding,
payload: { title: 'RAG explique', category: 'ai', year: 2026 },
}],
});
// Recherche avec filtre sur le payload
const results = await client.search('articles', {
vector: queryEmbedding,
limit: 5,
filter: {
must: [{ key: 'category', match: { value: 'ai' } }],
},
with_payload: true,
});
results.forEach((r) => console.log(r.score.toFixed(3), r.payload.title));
Weaviate : modules et hybrid search
Weaviate va plus loin que le simple stockage de vecteurs : il integre des modules de vectorisation (il peut appeler OpenAI/Cohere pour vous), une recherche hybride native et une API GraphQL.
// weaviate-demo.js — recherche hybride native
import weaviate from 'weaviate-client';
const client = await weaviate.connectToLocal();
const collection = client.collections.get('Article');
// Recherche hybride : combine vectoriel (semantique) + BM25 (mots-cles)
const result = await collection.query.hybrid('integration api temps reel', {
limit: 5,
alpha: 0.5, // 0 = full keyword, 1 = full vector, 0.5 = equilibre
returnMetadata: ['score'],
});
result.objects.forEach((obj) => {
console.log(obj.metadata.score.toFixed(3), obj.properties.title);
});
Le parametre alpha regle le dosage entre semantique et mots-cles : c'est l'atout differenciant de Weaviate pour les cas ou les termes exacts comptent autant que le sens.
pgvector : rester sur PostgreSQL
Si votre application tourne deja sur PostgreSQL, l'extension pgvector ajoute le type vector et la recherche par similarite sans introduire une nouvelle brique d'infrastructure. C'est souvent le choix le plus pragmatique pour demarrer.
-- Activer l'extension et creer une table avec colonne vectorielle
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE articles (
id bigserial PRIMARY KEY,
title text,
category text,
embedding vector(1536) -- dimension de l'embedding
);
-- Index HNSW pour une recherche approximative rapide
CREATE INDEX ON articles
USING hnsw (embedding vector_cosine_ops);
-- Recherche des 5 plus proches avec filtre SQL classique
SELECT title, 1 - (embedding <=> $1) AS similarity
FROM articles
WHERE category = 'ai'
ORDER BY embedding <=> $1 -- operateur de distance cosinus
LIMIT 5;
Tableau comparatif complet
| Critere | Pinecone | Qdrant | Weaviate | pgvector |
|---|---|---|---|---|
| Modele | Managed only | OSS + cloud | OSS + cloud | Extension PG |
| Self-host | Non | Oui (Docker) | Oui (Docker/K8s) | Oui |
| Langage | Proprietaire | Rust | Go | C (PostgreSQL) |
| Hybrid search | Partiel | Oui | Oui (natif) | Manuel (RRF) |
| Filtrage avance | Bon | Excellent | Bon | SQL complet |
| Vectorisation integree | Non | Non | Oui (modules) | Non |
| Scaling horizontal | Auto | Sharding | Sharding | Vertical surtout |
| Ideal pour | Zero ops | Perf + controle | Hybrid + modules | Stack PG existante |
Recherche hybride en pratique
La recherche purement vectorielle rate parfois les termes exacts (references, codes, noms propres). La recherche hybride fusionne vectoriel et mots-cles. Avec pgvector, on l'implemente manuellement via Reciprocal Rank Fusion (RRF).
-- Recherche hybride manuelle avec pgvector + recherche plein texte
WITH semantic AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $1) AS rank
FROM articles ORDER BY embedding <=> $1 LIMIT 20
),
keyword AS (
SELECT id, ROW_NUMBER() OVER (
ORDER BY ts_rank(to_tsvector('french', title), plainto_tsquery('french', $2)) DESC
) AS rank
FROM articles
WHERE to_tsvector('french', title) @@ plainto_tsquery('french', $2)
LIMIT 20
)
-- Fusion RRF : 1/(k + rang) somme les deux classements
SELECT a.title,
COALESCE(1.0/(60 + s.rank), 0) + COALESCE(1.0/(60 + k.rank), 0) AS rrf_score
FROM articles a
LEFT JOIN semantic s ON s.id = a.id
LEFT JOIN keyword k ON k.id = a.id
WHERE s.id IS NOT NULL OR k.id IS NOT NULL
ORDER BY rrf_score DESC
LIMIT 5;
Pipeline d'ingestion cote application
Choisir un moteur n'est que la moitie du travail. Dans une vraie application, il faut un pipeline d'ingestion : decouper les documents en chunks, generer les embeddings, puis les inserer en lot. La qualite de cette etape conditionne davantage la pertinence des resultats que le moteur lui-meme.
// ingest.js — chunking + embeddings + upsert par lots
import OpenAI from 'openai';
import { QdrantClient } from '@qdrant/js-client-rest';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const qdrant = new QdrantClient({ url: process.env.QDRANT_URL });
// 1. Decouper un texte en chunks avec recouvrement (overlap)
function chunkText(text, size = 800, overlap = 100) {
const chunks = [];
for (let i = 0; i < text.length; i += size - overlap) {
chunks.push(text.slice(i, i + size));
}
return chunks;
}
// 2. Generer les embeddings par batch (limiter les appels API)
async function embedBatch(textes) {
const res = await openai.embeddings.create({
model: 'text-embedding-3-small', // 1536 dim, bon rapport cout/qualite
input: textes, // batch = 1 seul appel pour N textes
});
return res.data.map((d) => d.embedding);
}
// 3. Pipeline complet pour un document
async function ingestDocument(doc) {
const chunks = chunkText(doc.content);
const vectors = await embedBatch(chunks);
await qdrant.upsert('articles', {
points: chunks.map((chunk, i) => ({
id: `${doc.id}-${i}`,
vector: vectors[i],
payload: { docId: doc.id, title: doc.title, chunk, position: i },
})),
});
console.log(`${chunks.length} chunks indexes pour ${doc.title}`);
}
Exposer la recherche au front-end
Cote application web, on n'appelle jamais la base vectorielle directement depuis le navigateur : la cle API et la logique d'embedding restent cote serveur. On expose un endpoint REST minimal qui transforme la requete texte en vecteur, interroge la base et renvoie un JSON propre au front.
// search-api.js — endpoint Express de recherche semantique
import express from 'express';
const app = express();
app.use(express.json());
app.post('/api/search', async (req, res) => {
const { query, category } = req.body;
if (!query || query.length > 500) {
return res.status(400).json({ error: 'Requete invalide' });
}
// 1. Embedding de la requete utilisateur (cote serveur)
const [vector] = await embedBatch([query]);
// 2. Recherche filtree dans Qdrant
const hits = await qdrant.search('articles', {
vector,
limit: 5,
filter: category
? { must: [{ key: 'category', match: { value: category } }] }
: undefined,
with_payload: true,
});
// 3. Reponse minimale et serialisable pour le front
res.json(hits.map((h) => ({
title: h.payload.title,
extrait: h.payload.chunk.slice(0, 160),
score: Number(h.score.toFixed(3)),
})));
});
app.listen(3000);
// front.js — appel fetch depuis l'interface
async function rechercher(query) {
const r = await fetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, category: 'ai' }),
});
if (!r.ok) throw new Error('Recherche indisponible');
const resultats = await r.json();
// resultats = [{ title, extrait, score }, ...] prets a afficher
return resultats;
}
Performance, dimensions et couts
Deux leviers pesent lourd sur la facture et la latence : la dimension des embeddings et la quantization. Reduire les dimensions (text-embedding-3-small permet de tronquer a 512 ou 256) divise le stockage et accelere la recherche, au prix d'une legere perte de rappel.
| Levier | Effet stockage | Effet latence | Compromis |
|---|---|---|---|
| Dimensions 1536 → 512 | -66 % | Plus rapide | Rappel legerement reduit |
| Quantization scalaire (int8) | -75 % | Plus rapide | Precision quasi intacte |
| Quantization binaire | -97 % | Tres rapide | Necessite re-scoring |
| Index HNSW : m eleve | + memoire | Meilleur rappel | Indexation plus lente |
Pour une application a fort trafic, la combinaison gagnante est souvent : embeddings tronques a 512–768 dimensions, quantization scalaire activee cote moteur, et un cache des requetes frequentes. Surveillez le rappel@k (proportion de bons resultats dans le top k) avant et apres chaque optimisation : une recherche rapide mais imprecise ruine l'experience.
Arbre de decision
// Pseudo-code d'aide a la decision
function choisirBaseVectorielle(ctx) {
// Deja sur PostgreSQL et volume modere ?
if (ctx.dejaPostgres && ctx.vecteurs < 1_000_000) {
return 'pgvector'; // moins d'infra, transactions ACID
}
// Pas d'equipe ops et budget cloud ok ?
if (!ctx.equipeOps) {
return 'Pinecone'; // managed, zero maintenance
}
// Besoin de hybrid search + vectorisation integree ?
if (ctx.hybridSearch && ctx.vectorisationManagee) {
return 'Weaviate';
}
// Performance, filtrage avance, self-hosting maitrise ?
return 'Qdrant'; // meilleur compromis open source
}
- Prototype / petit volume + PostgreSQL : pgvector
- Lancer vite sans ops : Pinecone
- Performance + controle open source : Qdrant
- Hybrid search riche + modules : Weaviate
Erreurs frequentes a l'integration
La plupart des problemes de recherche vectorielle ne viennent pas du moteur mais de la maniere dont on l'integre. Premiere erreur classique : melanger des embeddings de modeles differents dans une meme collection. Un vecteur produit par text-embedding-3-small n'est pas comparable a un vecteur d'un autre modele ; les distances calculees n'ont alors plus aucun sens. Figez le modele d'embedding et reindexez tout si vous en changez.
Deuxieme piege : oublier de normaliser la dimension et la metrique a la creation de la collection. Si vous declarez une distance euclidienne alors que vos embeddings sont concus pour le cosinus, le rappel s'effondre silencieusement — la recherche fonctionne, mais renvoie de mauvais resultats. Verifiez toujours que la metrique de la collection correspond a celle recommandee par le modele d'embedding.
Troisieme source de bugs cote application web : vectoriser la requete avec un modele different de celui des documents. La requete et le corpus doivent passer par le meme modele d'embedding, sans quoi ils vivent dans des espaces incompatibles. Enfin, ne renvoyez jamais les vecteurs bruts au front : ils sont volumineux et inutiles cote client. Ne serialisez que les metadonnees et le score, comme dans l'endpoint vu plus haut.
Conclusion
Il n'existe pas de « meilleure » base vectorielle dans l'absolu, seulement la mieux adaptee a votre contexte. pgvector minimise l'infrastructure si vous etes deja sur PostgreSQL. Pinecone elimine l'ops. Qdrant offre le meilleur compromis open source performance / simplicite. Weaviate excelle des que la recherche hybride et la vectorisation integree comptent.
Commencez simple : pour la majorite des projets RAG, pgvector ou Qdrant suffisent largement. Ne migrez vers une solution plus complexe que lorsque le volume, le filtrage ou le scaling le justifient reellement. La vraie difficulte d'un systeme de recherche reste la qualite du chunking et des embeddings, pas le choix du moteur de stockage.
- Toutes utilisent HNSW pour la recherche approximative rapide
- La difference se joue sur ops, filtrage, hybrid search et cout
- pgvector pour rester simple sur une stack PostgreSQL
- Qdrant pour le meilleur open source self-hosted
- La qualite des embeddings prime sur le choix du moteur