Intelligence Artificielle angularforall.com

- Vector DB comparees : Pinecone, Qdrant, Weaviate

Base-Vectorielle Vector-Database Pinecone Qdrant Weaviate Pgvector Rag Embeddings Recherche-Semantique Hnsw Hybrid-Search Ia-Generative Postgresql Node-Js
Vector DB comparees : Pinecone, Qdrant, Weaviate

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.

Le coeur du sujet : toutes ces bases utilisent des index de type HNSW (Hierarchical Navigable Small World) pour une recherche approximative ultra-rapide. La difference se joue sur l'operabilite, le filtrage, le scaling et le modele economique.

Criteres de choix

Les questions a se poser avant de choisir :
  • 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}`);
});
Pinecone — quand le choisir : equipe sans ops, time-to-market prioritaire, volumes importants sans envie de gerer l'infra. Cout : facturation a l'usage (par pod ou serverless), qui grimpe avec le volume et le trafic.

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));
Qdrant brille sur le filtrage : il combine filtres et recherche vectorielle de maniere tres efficace grace a son index de payload, evitant le piege du « filtrer apres » qui degrade le rappel.

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.

Weaviate — quand le choisir : besoin de recherche hybride out-of-the-box, vectorisation integree pour eviter de gerer le pipeline d'embeddings, ou appetit pour GraphQL. Plus riche mais aussi plus lourd a operer que Qdrant.

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;
L'avantage decisif : vos vecteurs vivent a cote de vos donnees relationnelles. Un seul JOIN suffit pour combiner recherche semantique et donnees metier, le tout dans des transactions ACID. Voir notre guide dedie embeddings + pgvector.

Tableau comparatif complet

CriterePineconeQdrantWeaviatepgvector
ModeleManaged onlyOSS + cloudOSS + cloudExtension PG
Self-hostNonOui (Docker)Oui (Docker/K8s)Oui
LangageProprietaireRustGoC (PostgreSQL)
Hybrid searchPartielOuiOui (natif)Manuel (RRF)
Filtrage avanceBonExcellentBonSQL complet
Vectorisation integreeNonNonOui (modules)Non
Scaling horizontalAutoShardingShardingVertical surtout
Ideal pourZero opsPerf + controleHybrid + modulesStack 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;
Avec Qdrant ou Weaviate, cette logique est integree : un seul appel API renvoie le resultat fusionne. C'est l'un des principaux arguments en faveur d'une base dediee des que la recherche hybride devient centrale.

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}`);
}
Le recouvrement (overlap) evite de couper une idee en plein milieu de deux chunks. Une regle saine : chunks de 500 a 1000 caracteres, overlap de 10 a 15 %. Conservez toujours le texte original dans le payload pour pouvoir le renvoyer au LLM lors de la generation RAG.

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;
}
Securite : validez toujours la longueur et le type de la requete avant de la vectoriser (un embedding sur un texte de plusieurs Mo coute cher et peut servir d'attaque par deni de service). Ajoutez un rate limit par IP sur cet endpoint.

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.

LevierEffet stockageEffet latenceCompromis
Dimensions 1536 → 512-66 %Plus rapideRappel legerement reduit
Quantization scalaire (int8)-75 %Plus rapidePrecision quasi intacte
Quantization binaire-97 %Tres rapideNecessite re-scoring
Index HNSW : m eleve+ memoireMeilleur rappelIndexation 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
}
Recommandations rapides :
  • 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.

A retenir :
  • 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

Partager