RAG & Connaissances Par

Qdrant ou pgvector : quelle base vectorielle pour un RAG ?

Entre Qdrant et pgvector, la première question à se poser pour un RAG en production est simple : avez-vous déjà PostgreSQL en production ? Si oui, et si votre corpus tient en quelques millions de chunks, pgvector suffit : un index HNSW, vos filtres en SQL, vos droits d'accès en row-level security, vos sauvegardes habituelles. Qdrant est le meilleur choix quand la recherche est au cœur du produit : un SaaS avec des centaines de clients à cloisonner, une recherche hybride dense et sparse en une seule requête, de la quantification pour tenir des dizaines de millions de vecteurs, ou une base de recherche à faire évoluer sans toucher à la base transactionnelle.

Plusieurs des RAG que nous avons livrés depuis 2025 s'appuient sur Qdrant : le SaaS d'agents de service client Heeya, l'IA conversationnelle de Reezet, un RAG législatif pour le secteur public, le module RAG d'un éditeur de plateforme IA. Ce n'est pas un choix de principe : ces projets cumulaient multi-clients, recherche hybride et index mis à jour en continu. Nous n'avons pas de mission en production sur pgvector à citer ; ce qui suit de ce côté s'appuie sur la documentation du projet.

Cet article compare les deux sur les critères qui comptent une fois en production : volume et index, filtrage par métadonnées et multi-tenant, recherche hybride, mises à jour, sauvegardes, hébergement. Pour le panorama des huit principales bases vectorielles (Chroma, Weaviate, Milvus, Pinecone...), voyez notre comparatif des bases de données vectorielles.

Points clés à retenir

  • Si PostgreSQL est déjà en production et que le corpus tient en quelques millions de chunks, pgvector évite un composant de plus et supprime la synchronisation entre base métier et base vectorielle
  • Qdrant est plus adapté quand la recherche est le produit : multi-tenant à grande échelle, recherche hybride dense et sparse native, quantification, base de recherche séparée de la base transactionnelle
  • Le filtrage par métadonnées se fait en WHERE SQL et row-level security côté Postgres, en filtres de payload côté Qdrant ; avec pgvector, un filtre sélectif demande les parcours itératifs de la version 0.8
  • La recherche hybride est native dans Qdrant (Query API, fusion RRF ou DBSF) ; dans Postgres, elle s'assemble avec tsvector ou pg_search et une fusion écrite en SQL
  • Dans les deux cas, l'index vectoriel est une donnée dérivée : gardez les chunks et la version du modèle d'embeddings pour pouvoir le reconstruire
Critère pgvector (PostgreSQL) Qdrant
Nature Extension d'une base relationnelle existante Moteur de recherche vectorielle dédié, écrit en Rust
Index HNSW ou IVFFlat ; HNSW limité à 2 000 dimensions en vector, 4 000 en halfvec HNSW, vecteurs en RAM ou sur disque
Filtrage par métadonnées WHERE SQL, jointures ; parcours itératifs depuis la 0.8 pour les filtres sélectifs Filtres de payload appliqués pendant le parcours du graphe
Multi-tenant Row-level security, index partiels, partitionnement Index de payload is_tenant, shards dédiés pour les gros clients
Recherche hybride tsvector ou pg_search, fusion à écrire en SQL Vecteurs sparse et BM25, fusion RRF ou DBSF dans la Query API
Quantification halfvec, quantification binaire par index d'expression Scalaire, binaire, produit, avec rescoring intégré
Sauvegardes Celles de PostgreSQL, cohérentes avec les données métier Snapshots par collection, à coordonner avec la base métier
Profil type Application déjà sur Postgres, corpus de quelques millions de chunks SaaS multi-clients, recherche hybride, gros volumes

Qdrant ou pgvector : la première question est votre PostgreSQL

Le critère qui pèse le plus dans le choix entre Qdrant et pgvector n'est pas la performance, c'est l'architecture existante. Si votre application tourne déjà sur PostgreSQL, pgvector ajoute la recherche vectorielle dans la base que vous savez déjà sauvegarder, superviser et restaurer. Qdrant ajoute un service de plus, avec ses propres sauvegardes et sa propre synchronisation.

Ce que pgvector apporte quand Postgres est déjà là

Les chunks et leurs embeddings vivent dans la même base que les documents, les utilisateurs et les droits. Supprimer un document supprime ses chunks dans la même transaction, par une clé étrangère en ON DELETE CASCADE. Filtrer par service, par date ou par statut, c'est une clause WHERE ou une jointure sur des tables qui existent déjà.

Le schéma de départ tient en quelques lignes :

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE chunks (
  id          bigserial PRIMARY KEY,
  document_id bigint NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
  tenant_id   uuid NOT NULL,
  contenu     text NOT NULL,
  embedding   halfvec(3072) NOT NULL
);

CREATE INDEX ON chunks USING hnsw (embedding halfvec_cosine_ops)
  WITH (m = 16, ef_construction = 64);

Le type halfvec n'est pas là par hasard : selon la documentation de pgvector, un index HNSW sur le type vector est limité à 2 000 dimensions, contre 4 000 en halfvec. Un modèle d'embeddings à 3 072 dimensions, comme text-embedding-3-large d'OpenAI, ne s'indexe donc qu'en demi-précision ou en réduisant les dimensions.

Ce que Qdrant apporte comme service séparé

Qdrant sépare la charge de recherche de la charge transactionnelle. Une montée en charge des questions posées à l'assistant ne ralentit pas les écritures de l'application, et la base de recherche se dimensionne à part (RAM, disque, réplicas). Chaque point porte un payload JSON (identifiant du client, du document, langue, date) sur lequel on filtre pendant la recherche.

La contrepartie, c'est la synchronisation. Quand un document est modifié ou supprimé dans la base métier, il faut le répercuter dans Qdrant, sinon l'assistant cite un texte qui n'existe plus. Ce flux d'ingestion se conçoit dès le départ, avec un identifiant de document commun aux deux bases.

Avant de changer de base

Quand un RAG répond mal, la base vectorielle est rarement en cause. En audit, les causes les plus fréquentes que nous trouvons sont des bugs dans la chaîne de traitement : un modèle d'embeddings différent à l'indexation et à la recherche, un seul chunk récupéré par erreur, une recherche qui ne prend que le dernier message au lieu de toute la conversation. Changer de base ne corrige aucun de ces trois problèmes.

Volume de vecteurs, index HNSW et quantification

Qdrant et pgvector utilisent le même algorithme d'index, HNSW, et sont tous deux rapides tant que l'index tient en mémoire. La question du volume revient donc à un calcul de RAM : 1 million de vecteurs de 1 024 dimensions en float32 occupe environ 4 Go, avant même le graphe HNSW. En demi-précision, environ 2 Go. En quantification binaire, environ 128 Mo.

Les réglages côté pgvector

Les valeurs par défaut de pgvector sont m = 16 et ef_construction = 64 à la construction, et hnsw.ef_search = 40 à la requête. Augmenter ef_search améliore le rappel au prix de la latence ; c'est le premier paramètre à mesurer sur votre jeu de test. La construction de l'index est nettement plus rapide quand le graphe tient dans maintenance_work_mem, et mieux vaut créer l'index après le chargement initial qu'avant.

Pour réduire l'empreinte, pgvector propose halfvec (moitié de la mémoire) et une quantification binaire par index d'expression, suivie d'un second tri sur les vecteurs complets :

CREATE INDEX ON chunks
  USING hnsw ((binary_quantize(embedding)::bit(3072)) bit_hamming_ops);

SELECT id FROM (
  SELECT id, embedding FROM chunks
  ORDER BY binary_quantize(embedding)::bit(3072) <~> binary_quantize($1)
  LIMIT 100
) candidats
ORDER BY embedding <=> $1
LIMIT 10;

Les réglages côté Qdrant

Qdrant expose les mêmes paramètres HNSW (m, ef_construct, hnsw_ef à la requête) et ajoute deux leviers sans équivalent direct dans Postgres. Les vecteurs d'origine peuvent rester sur disque pendant que l'index et une version quantifiée restent en RAM. Et la quantification (scalaire en int8, binaire ou par produit) se déclare dans la configuration de la collection, avec un rescoring sur les vecteurs d'origine géré par le moteur.

Sur un corpus de quelques dizaines de millions de chunks, cette combinaison permet de tenir sur une machine raisonnable sans écrire de requête en deux temps. La quantification binaire dégrade le rappel sur certains modèles d'embeddings : elle se valide sur votre jeu de questions, pas sur la foi d'un benchmark.

Où le volume tranche

Jusqu'à quelques millions de chunks, les deux tiennent sans difficulté, et le volume ne départage pas. Au-delà de la dizaine de millions, pgvector reste possible, mais la base vectorielle consomme la RAM et le cache de la base applicative : c'est souvent là qu'une base dédiée se justifie. Pour situer : 5 000 documents de 20 pages, découpés en chunks d'environ 500 tokens, donnent de l'ordre de 100 000 chunks.

Filtrage par métadonnées et droits d'accès : WHERE et RLS, ou payload Qdrant

Dans un RAG d'entreprise, presque chaque recherche est filtrée : par client, par service, par niveau de confidentialité, par date de validité. Le filtrage par métadonnées est aussi le mécanisme qui applique les droits d'accès, et c'est le critère sur lequel pgvector et Qdrant diffèrent le plus dans leur fonctionnement.

pgvector : WHERE, row-level security et parcours itératifs

Avec un index HNSW, pgvector récupère d'abord les ef_search candidats les plus proches, puis applique le filtre. Si le filtre ne garde que 2 % des lignes, il peut ne rester aucun résultat sur 40 candidats. Depuis la version 0.8.0, les parcours itératifs (hnsw.iterative_scan) continuent à parcourir l'index jusqu'à obtenir assez de résultats. Pour un très gros client, un index partiel ou une partition dédiée reste possible.

Pour le multi-tenant, la row-level security de PostgreSQL place le cloisonnement dans la base elle-même : même si le code oublie le filtre, la base ne renvoie pas les lignes d'un autre client.

ALTER TABLE chunks ENABLE ROW LEVEL SECURITY;

CREATE POLICY isolation_client ON chunks
  USING (tenant_id = current_setting('app.tenant_id')::uuid);

-- dans chaque transaction de recherche
SET LOCAL app.tenant_id = '6f1c...';
SET LOCAL hnsw.iterative_scan = relaxed_order;

SELECT id, contenu FROM chunks
ORDER BY embedding <=> $1
LIMIT 10;

Deux détails à connaître : la politique ne s'applique pas au propriétaire de la table (sauf FORCE ROW LEVEL SECURITY), donc l'application doit se connecter avec un rôle dédié ; et la condition de la politique est elle aussi appliquée après le parcours de l'index, d'où l'intérêt du parcours itératif.

Qdrant : filtres de payload et index is_tenant

Qdrant applique les filtres de payload pendant le parcours du graphe HNSW, pas après. Pour le multi-tenant, sa documentation sur le multi-tenant recommande une seule collection pour tous les clients, avec un index de payload déclaré is_tenant (depuis la version 1.11), qui regroupe sur le stockage les vecteurs d'un même client.

from qdrant_client import QdrantClient, models

client = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY)

client.create_payload_index(
    collection_name="chunks",
    field_name="tenant_id",
    field_schema=models.KeywordIndexParams(type="keyword", is_tenant=True),
)

filtre_client = models.Filter(must=[
    models.FieldCondition(key="tenant_id", match=models.MatchValue(value=tenant_id)),
])

resultats = client.query_points(
    collection_name="chunks",
    query=vecteur_question,
    query_filter=filtre_client,
    limit=10,
)

Pour indexer plus vite quand les clients sont nombreux, la même documentation propose de désactiver le graphe global (m = 0) et de construire un graphe par client (payload_m = 16). Depuis la version 1.16, le multi-tenant par paliers déplace les gros clients vers des shards dédiés, avec un seuil indicatif d'environ 20 000 points.

Le point commun : le filtre se décide côté serveur

Qdrant n'a pas de row-level security au sens de PostgreSQL : dans l'architecture la plus courante, c'est votre API qui ajoute le filtre client à chaque requête, à partir de l'identité authentifiée, jamais à partir d'un paramètre envoyé par le navigateur. C'est la situation de Heeya, SaaS d'agents de service client utilisé par plus de 200 entreprises : leurs documents sont indexés dans Qdrant, et aucune ne doit pouvoir faire remonter les documents d'une autre. Pour le détail des droits d'accès et des attaques par injection de prompt, voir l'article de ianas.fr sur la sécurisation d'un RAG.

Recherche hybride : sparse et BM25 dans Qdrant, tsvector ou pg_search dans Postgres

La recherche hybride combine une recherche sémantique (vecteurs denses) et une recherche lexicale (mots exacts), puis fusionne les deux classements. Elle est utile dès que les questions contiennent des références, des codes produit, des numéros d'article ou du vocabulaire métier que les embeddings rapprochent mal. Qdrant la fait en une requête ; dans Postgres, elle s'assemble.

Dans Qdrant : la Query API

Depuis la version 1.10, la Query API de Qdrant lance plusieurs sous-requêtes (prefetch) sur des vecteurs nommés, denses et sparse, puis les fusionne en Reciprocal Rank Fusion (RRF) ou, depuis la 1.11, en Distribution-Based Score Fusion (DBSF). Les vecteurs sparse peuvent porter un BM25 ou un modèle appris comme SPLADE.

resultats = client.query_points(
    collection_name="chunks",
    prefetch=[
        models.Prefetch(query=vecteur_dense, using="dense",
                        filter=filtre_client, limit=40),
        models.Prefetch(query=models.SparseVector(indices=idx, values=poids),
                        using="bm25", filter=filtre_client, limit=40),
    ],
    query=models.FusionQuery(fusion=models.Fusion.RRF),
    limit=10,
)

C'est l'architecture retenue pour un éditeur de logiciel médical : documentation et transcriptions vidéo dans Qdrant, recherche hybride BM25 et embeddings fusionnée en RRF pour retrouver les termes techniques exacts. Même choix pour le module RAG d'un éditeur de plateforme IA, qui indexe des PDF, des présentations et des documents Word.

Dans Postgres : tsvector, pg_search et une fusion en SQL

PostgreSQL a sa recherche plein texte (tsvector, index GIN, configuration french). La fusion RRF s'écrit avec deux sous-requêtes :

WITH semantique AS (
  SELECT id, row_number() OVER (ORDER BY dist) AS rang
  FROM (SELECT id, embedding <=> $1 AS dist FROM chunks
        ORDER BY dist LIMIT 40) s
),
lexicale AS (
  SELECT id, row_number() OVER (ORDER BY score DESC) AS rang
  FROM (SELECT id, ts_rank(tsv, q) AS score
        FROM chunks, websearch_to_tsquery('french', $2) AS q
        WHERE tsv @@ q
        ORDER BY score DESC LIMIT 40) l
)
SELECT id, sum(1.0 / (60 + rang)) AS score_rrf
FROM (SELECT * FROM semantique UNION ALL SELECT * FROM lexicale) r
GROUP BY id
ORDER BY score_rrf DESC
LIMIT 10;

Une nuance : ts_rank n'est pas un BM25, il ne pondère pas les termes selon leur rareté dans le corpus. Pour un vrai BM25 dans Postgres, l'extension pg_search de ParadeDB existe, mais elle figure rarement dans les offres PostgreSQL managées. pgvector propose aussi un type sparsevec pour des vecteurs SPLADE, indexable jusqu'à 1 000 éléments non nuls. Pour le principe et les réglages de la recherche hybride, voyez l'article de ianas.fr sur le RAG hybride BM25 et vectoriel.

Mises à jour de l'index, sauvegardes et hébergement souverain

Un RAG en production voit ses documents changer en continu, et c'est l'exploitation qui coûte le plus sur la durée. pgvector hérite de tout l'outillage PostgreSQL ; Qdrant demande ses propres procédures, mais elles sont simples.

Mettre à jour l'index sans le reconstruire

Les deux bases gèrent les ajouts et suppressions sans reconstruction complète. Dans Postgres, un UPDATE ou un DELETE laisse des lignes mortes que le VACUUM nettoie ; sur une table de chunks très remaniée, l'autovacuum se surveille et un REINDEX CONCURRENTLY ponctuel peut s'imposer. Dans Qdrant, un point se remplace par son identifiant (upsert) et une suppression peut viser un filtre, par exemple tous les points d'un document_id ; l'optimiseur réorganise les segments en arrière-plan.

Sur un RAG législatif pour le secteur public, l'index Qdrant se met à jour automatiquement quand les textes évoluent. La règle de conception qui rend cela fiable : chaque chunk porte l'identifiant et la version de son document source, et une mise à jour supprime tous les chunks de l'ancienne version avant d'insérer les nouveaux. Notre article sur la maintenance d'un RAG en production détaille le reste du suivi.

Sauvegarder : l'index est une donnée dérivée

Avec pgvector, la sauvegarde PostgreSQL (dump, restauration à un instant donné, sauvegardes de l'offre managée) contient les vecteurs et les documents dans un état cohérent. Avec Qdrant, les snapshots se font par collection et s'envoient vers un stockage objet ; une restauration doit être cohérente avec la base métier, sinon des chunks orphelins réapparaissent.

Dans les deux cas, gardez de quoi reconstruire : les chunks en texte, les métadonnées et le nom exact du modèle d'embeddings avec sa version. Un index se recalcule ; un index calculé avec un autre modèle que celui utilisé à la recherche donne des résultats faux sans aucune erreur visible.

Héberger en France

Les deux s'hébergent sur une infrastructure française. pgvector est disponible dans la plupart des offres PostgreSQL managées, dont celles d'OVHcloud et de Scaleway : vérifiez la version proposée, car les parcours itératifs demandent pgvector 0.8. Qdrant est open source sous licence Apache 2.0 et s'auto-héberge en conteneur sur une VM ou un Kubernetes chez OVHcloud, Scaleway ou Outscale. Qdrant Cloud, la version managée, est proposé sur AWS, Google Cloud et Azure, ce qui pose la question du droit applicable plutôt que celle de la région. Notre article sur le RAG souverain avec Mistral traite ces arbitrages, et celui sur le déploiement d'un RAG sur AWS avec Bedrock et Qdrant couvre le cas où AWS est déjà retenu.

Quand choisir pgvector, quand choisir Qdrant, et où se place Pinecone

Le choix entre Qdrant et pgvector se fait sur quatre questions : l'existant PostgreSQL, le nombre de clients à cloisonner, le besoin de recherche hybride et le volume. Aucune des deux n'est un mauvais choix pour un RAG de quelques centaines de milliers de chunks.

pgvector est le bon choix si :

  • votre application tourne déjà sur PostgreSQL et l'équipe sait l'exploiter ;
  • le corpus tient en quelques millions de chunks ;
  • les droits d'accès reposent sur des tables existantes, que vous voulez réutiliser en SQL et en row-level security ;
  • vous voulez une seule sauvegarde et aucune synchronisation entre deux bases.

Qdrant est le bon choix si :

  • la recherche est au cœur du produit, par exemple un SaaS qui sert de nombreux clients sur la même collection ;
  • la recherche hybride dense et sparse est nécessaire et vous ne voulez pas l'écrire en SQL ;
  • le volume dépasse la dizaine de millions de vecteurs ou la RAM est contrainte, et la quantification avec rescoring est utile ;
  • vous voulez séparer la charge de recherche de la base transactionnelle.

Pinecone se place ailleurs : un service entièrement managé, sans serveur à exploiter, non auto-hébergeable. Nous l'avons utilisé en 2024 sur des projets e-commerce et industriels où la mise en route rapide comptait plus que le lieu d'hébergement. Quand l'hébergement en France ou la maîtrise de l'infrastructure est une exigence, il sort de la liste.

Dans un projet de développement RAG sur mesure, nous tranchons ce choix pendant le cadrage, en partant de votre architecture et de vos contraintes de droits, puis nous le vérifions sur un jeu de questions réelles avant la mise en production. Pour situer la base vectorielle dans l'ensemble du chantier (ingestion, évaluation, coût), voyez notre guide du RAG en entreprise.

Questions fréquentes sur Qdrant et pgvector pour un RAG

Sur quelques millions de vecteurs et des requêtes sans filtre très sélectif, les deux répondent assez vite avec un index HNSW bien réglé pour que la latence d'un RAG se joue surtout dans l'appel au modèle de langage. Les écarts apparaissent avec des filtres sélectifs, des dizaines de millions de vecteurs ou une base PostgreSQL déjà chargée par l'application. Les benchmarks publiés par chaque éditeur favorisent en général leur produit : le seul test qui compte se fait sur vos données, avec vos filtres et votre volume de requêtes.
Oui, mais en l'assemblant soi-même. PostgreSQL fournit la recherche plein texte (tsvector, index GIN, ts_rank), pgvector fournit la recherche vectorielle et un type sparsevec pour des vecteurs creux, et la fusion des deux classements, par exemple en Reciprocal Rank Fusion, s'écrit en SQL. ts_rank n'est pas un BM25 au sens strict ; pour un vrai BM25 dans Postgres, l'extension pg_search de ParadeDB existe, mais elle est rarement proposée par les offres PostgreSQL managées.
Il n'y a pas de plafond fixe : la limite pratique est la mémoire. Un index HNSW est rapide quand il tient en RAM, et 1 million de vecteurs de 1024 dimensions en float32 occupe environ 4 Go avant même le graphe. Jusqu'à quelques millions de chunks, pgvector tient sans difficulté sur une instance correctement dimensionnée. Au-delà, le type halfvec et la quantification binaire réduisent l'empreinte, mais la base vectorielle partage alors ses ressources avec la charge transactionnelle de l'application.
La documentation Qdrant recommande une seule collection pour tous les clients, avec un champ de payload qui identifie le client et un index de payload déclaré avec is_tenant (depuis la version 1.11), qui regroupe physiquement les vecteurs de chaque client. Le filtre sur ce champ doit être imposé par votre API côté serveur, jamais transmis par le navigateur. Depuis la version 1.16, le multi-tenant par paliers permet de déplacer les gros clients vers des shards dédiés.
Oui. Une politique de row-level security s'applique à la table des chunks comme à n'importe quelle table, et la base refuse de renvoyer les lignes d'un autre client même si le code applicatif oublie le filtre. Point d'attention : la condition est appliquée après le parcours de l'index HNSW, donc un filtre très sélectif peut renvoyer moins de résultats que demandé. Les parcours itératifs de pgvector 0.8 corrigent ce comportement.
Oui, et sans recalculer les embeddings si le modèle reste le même : on exporte les vecteurs et leurs métadonnées depuis PostgreSQL, puis on les insère dans une collection Qdrant avec les mêmes identifiants. Ce qui demande du travail, c'est le code de recherche (filtres SQL à réécrire en filtres Qdrant, recherche hybride à refaire) et la synchronisation entre la base métier et la nouvelle base. Une couche d'accès qui isole la recherche du reste du code rend cette bascule beaucoup plus simple.
Le moteur Qdrant est open source sous licence Apache 2.0 : il s'auto-héberge gratuitement en conteneur Docker ou sur Kubernetes, le coût étant celui des serveurs et du temps d'exploitation. Qdrant Cloud, la version managée, est payant à la ressource avec un palier gratuit pour démarrer. pgvector est lui aussi open source et gratuit, et il est inclus dans la plupart des offres PostgreSQL managées.

Pour aller plus loin

Formation

Pour que vos équipes appliquent ces usages sur leurs propres documents, voir notre formation RAG, construite sur vos cas.

Assistants IA sur vos données

Un assistant qui répond juste, à partir de vos documents ?

On construit l'assistant sur vos documents et vos outils : réponses sourcées, droits d'accès respectés, qualité mesurée sur vos vraies questions avant la mise en service.

Pas sûr que ce soit rentable chez vous ? Estimez en 5 minutes le temps récupérable sur cette tâche, résultat immédiat et sans email.

Anas Rabhi, ingénieur IA et data scientist, fondateur de Tensoria
Anas R. Ingénieur IA, fondateur de Tensoria ianas.fr

Je suis ingénieur IA et data scientist, fondateur de Tensoria. Depuis plus de 6 ans, je conçois et développe des systèmes IA pour les entreprises : assistants internes basés sur RAG, agents IA en production, automatisations sur mesure, traitement intelligent de documents. Je prends le projet en charge du cadrage à la mise en production, sur les modèles du marché (Mistral, Claude, GPT) ou sur des modèles ouverts hébergés chez le client quand la confidentialité l'exige.