← Stack

Qdrant

pgvector est le bon point de départ. Qdrant s'impose quand la complexité des filtres, le volume du corpus ou la souveraineté des données dépassent ce qu'une extension Postgres peut offrir.

Vue d'ensemble

Qdrant est une base de données vectorielle Apache 2.0, écrite en Rust, conçue spécifiquement pour la recherche de similarité avec filtrage de métadonnées. Elle est développée par Qdrant Solutions GmbH, dont le siège est à Berlin, en Allemagne, et a levé 87,8 M$ en quatre tours de financement. Chaque enregistrement est un point : un identifiant unique, un vecteur dense (1 à 4096 dimensions) et un payload JSON optionnel. Les points appartiennent à des collections, l'équivalent des tables. La base est disponible sous forme de container Docker pour les déploiements auto-hébergés, et via Qdrant Cloud pour un hébergement géré.

La vraie question n'est pas de choisir entre Qdrant et pgvector dans l'abstrait. pgvector est le défaut recommandé quand votre corpus vectoriel réside dans une base Postgres existante, que vos requêtes se limitent à la similarité et que les jointures SQL ou les écritures transactionnelles comptent. Qdrant prend sa place quand trois signaux apparaissent : la complexité du filtrage de métadonnées (les requêtes combinant similarité vectorielle et conditions payload sélectives dégradent pgvector à grande échelle), la pression mémoire liée à la taille du corpus (TurboQuant et le stockage mappé en mémoire gèrent de grands corpus avec peu de RAM), et un SLA de latence p99 qui ne peut tolérer les pics que pgvector affiche sous charge concurrente au-delà de 1 à 5 millions de vecteurs. Commencer avec pgvector, mesurer le corpus et les patterns de requêtes, puis basculer quand une vraie limite est atteinte.

L'argument de souveraineté des données est concret pour Qdrant. Qdrant Solutions GmbH est immatriculée en Allemagne, ce qui élimine l'exposition au CLOUD Act applicable à tout hébergeur dont le siège est aux États-Unis, quelle que soit la localisation physique des données. La licence Apache 2.0 exclut toute facturation par requête, tout enfermement propriétaire et toute dépendance à un cloud commercial. Une instance Qdrant en production tourne dans un seul container Docker sur Hetzner (ISO 27001, conforme RGPD) ou Scaleway fr-par, la même infrastructure adaptée à l'inférence de modèles auto-hébergés. Pour les équipes qui construisent des pipelines RAG soumis au RGPD ou des mémoires d'agents, cette combinaison offre une recherche vectorielle dédiée en juridiction européenne exclusive, sans dépendance au cloud américain. Voir le pilier sovereign-ai pour l'arbre de décision complet sur la juridiction des données en Europe.

Architecture

Qdrant organise les données en collections : des espaces de noms isolés regroupant des ensembles de points. Chaque point porte un identifiant unique, un vecteur dense et un payload JSON optionnel à clés arbitraires. Le moteur de stockage est HNSW, avec une modification clé : les index de payload créés avant l'ingestion informent le graphe HNSW avec des arêtes conscientes des filtres. Une requête filtrée à 0,5 % du corpus navigue dans un sous-graphe pertinent plutôt que de parcourir tous les candidats pour en éliminer la majorité. La quantification (scalaire, binaire, TurboQuant) échange une perte de rappel contrôlée contre une réduction drastique de la mémoire : TurboQuant à 4 bits offre un rappel similaire à la quantification scalaire (SQ8) pour environ 2 fois moins de mémoire. Le sharding et la réplication s'ajoutent en couche supérieure pour la montée en charge distribuée et la haute disponibilité.

pgvectorPostgres déjà en placejointures SQL requisescorpus < 1M vecteursécritures transactionnellesmigrer quand →Qdrantfiltrage de payload complexecorpus > 1M vecteursSLA p99 sous chargeauto-hébergé EU, sans cloud US
pgvector vs Qdrant : signaux clés pour rester sur pgvector (gauche) ou passer à Qdrant (droite)

Concepts clés

Collection
Espace de noms nommé dans Qdrant pour un ensemble de points, analogue à une table. Chaque collection possède ses propres dimensions vectorielles, sa métrique de distance et sa configuration d'index de payload.
Point
L'enregistrement atomique dans Qdrant : un identifiant unique (entier ou UUID) + un vecteur dense (1 à 4096 flottants) + un payload JSON optionnel. Les points sont insérés ou mis à jour (upsert) dans des collections et recherchés par similarité vectorielle, avec filtrage optionnel sur le payload.
Payload
Métadonnées JSON arbitraires attachées à un point. Une clé de payload n'est filtrable que si un index de payload existe pour cette clé. Types supportés : keyword, integer, float, text (texte intégral), geo.
Index de payload
Index sur une clé de payload permettant un pré-filtrage rapide lors de la recherche vectorielle. Créer les index de payload AVANT le chargement des données : Qdrant intègre des arêtes conscientes des filtres dans le graphe HNSW lors de la construction. Ajouter des index après l'ingestion déclenche une reconstruction du graphe.
HNSW (avec filtrage)
Qdrant construit le graphe HNSW avec des arêtes qui tiennent compte des valeurs d'index de payload. Une recherche filtrée ne parcourt que le sous-graphe correspondant à la condition de filtre : la latence est proportionnelle à l'ensemble de résultats, et non au corpus complet. pgvector applique les filtres après la recherche, ce qui dégrade la latence quand les filtres sont sélectifs.
TurboQuant
Algorithme de quantification par rotation de Qdrant (v1.18, adapté de Google Research). Applique une rotation de Hadamard avant la compression en 4 bits (ou moins) pour redistribuer uniformément les valeurs vectorielles. À 4 bits, offre un rappel similaire à la quantification scalaire (SQ8) pour environ 2 fois moins de mémoire.
Shard
Partition horizontale d'une collection distribuée entre plusieurs noeuds pour la montée en charge horizontale. Qdrant gère le routage des shards en interne ; aucun coordinateur externe (etcd, ZooKeeper) n'est requis pour les déploiements mono-noeud ou les petits clusters.

Quand l'utiliser

Cas adaptés

  • Filtrage de métadonnées intensif : les requêtes combinent similarité vectorielle et conditions payload sélectives (département, plage de dates, tenant id, statut). Le HNSW avec filtrage de Qdrant navigue dans un sous-graphe correspondant au filtre ; pgvector filtre après coup et doit sur-récupérer des candidats quand le filtre est sélectif.
  • Grand corpus avec un SLA de latence p99 : à partir de 1 à 5 millions de vecteurs sous charge concurrente, la latence p95 de pgvector dépasse 100 ms dans de nombreuses configurations. Le moteur de stockage dédié de Qdrant et sa quantification maintiennent une latence de queue plus stable.
  • Infrastructure limitée en mémoire : TurboQuant et le stockage mappé en mémoire gèrent des corpus trop volumineux pour la RAM en précision float32 complète. 1 million de vecteurs à 1536 dimensions en TurboQuant 4 bits nécessite environ 1,2 Go contre 6 Go non compressé.
  • Stack auto-hébergée en Europe sans dépendance au cloud américain : Qdrant Solutions GmbH est une entreprise allemande, sous licence Apache 2.0, distribuée sous forme d'un seul container Docker. Auto-hébergée sur Hetzner ou Scaleway fr-par, elle n'a aucune exposition au CLOUD Act et aucune facturation par requête.
  • Mémoire d'agent avec un fort taux d'écriture/suppression : Qdrant supporte des cycles upsert et suppression efficaces, ce qui permet à un agent de maintenir une fenêtre de mémoire glissante sans la dégradation des scans liée à l'accumulation de vecteurs supprimés (tombstones) dans les stores en ajout seul.
  • RAG multi-tenant : un filtre payload sur un index keyword tenant_id isole les données de chaque tenant dans une collection partagée, sans index physiques séparés par tenant. Ajouter l'index de payload tenant_id avant l'ingestion pour bénéficier des arêtes de graphe conscientes des filtres.

Anti-patterns

  • Jointures SQL et requêtes relationnelles : Qdrant est un store vectoriel, pas une base de données relationnelle. Pour joindre des résultats de recherche vectorielle avec des tables relationnelles en une seule requête, pgvector dans Postgres gère cela nativement. Qdrant nécessite de récupérer les identifiants et d'effectuer la jointure dans le code applicatif.
  • Cohérence transactionnelle entre écritures vectorielles et relationnelles : si un upsert dans le store vectoriel doit réussir ou échouer atomiquement avec une écriture relationnelle, pgvector dans Postgres est le bon choix. Qdrant ne dispose d'aucune transaction ACID entre collections ou systèmes externes.
  • Petit corpus avec des requêtes de similarité simples : un corpus de moins de 200 000 vecteurs sans filtrage composé est largement dans la zone de confort de pgvector. Ajouter Qdrant comme service séparé engendre une charge opérationnelle sans bénéfice mesurable à cette échelle.
  • Aucun signal de production pour l'instant : commencer avec pgvector, instrumenter la latence p95/p99 et la sélectivité des filtres, puis basculer vers Qdrant quand une dégradation est observée sur des requêtes réelles. Ajouter Qdrant avant d'avoir mesuré des limites concrètes élargit la surface opérationnelle pour un gain incertain.

Exemples de code

Mise en place d'une collection : créer les index avant l'ingestion des données, puis upsert et recherche

# requires qdrant-client>=1.10
import numpy as np
from qdrant_client import QdrantClient
from qdrant_client.models import (
    Distance, VectorParams, PointStruct, PayloadSchemaType,
)

VECTOR_SIZE = 1536  # OpenAI text-embedding-3-small / Cohere embed

# Connect to a local Qdrant instance (Docker: qdrant/qdrant on port 6333)
client = QdrantClient(url="http://localhost:6333")

# Create a collection for 1536-dim embeddings, cosine distance
client.create_collection(
    collection_name="documents",
    vectors_config=VectorParams(size=VECTOR_SIZE, distance=Distance.COSINE),
)

# Create payload indexes BEFORE loading data.
# Qdrant builds filter-aware HNSW edges from existing payload indexes at
# build time. Filtering still works if you add an index later, but the
# filter-aware graph is then rebuilt (expensive on a large corpus).
client.create_payload_index(
    collection_name="documents",
    field_name="department",
    field_schema=PayloadSchemaType.KEYWORD,
)
client.create_payload_index(
    collection_name="documents",
    field_name="year",
    field_schema=PayloadSchemaType.INTEGER,
)

def fake_embedding() -> list[float]:
    # Placeholder for your model call (OpenAI, Cohere, local): text -> vector.
    # Returns a normalized 1536-dim vector so the snippet runs as-is.
    v = np.random.rand(VECTOR_SIZE)
    return (v / np.linalg.norm(v)).tolist()

# Upsert points: id + vector + arbitrary JSON payload
client.upsert(
    collection_name="documents",
    points=[
        PointStruct(
            id=1,
            vector=fake_embedding(),
            payload={"department": "finance", "year": 2025, "title": "Q4 report"},
        ),
        PointStruct(
            id=2,
            vector=fake_embedding(),
            payload={"department": "legal", "year": 2024, "title": "Contract template"},
        ),
    ],
)

# Plain similarity search (query_points replaces the removed search method)
results = client.query_points(
    collection_name="documents",
    query=fake_embedding(),
    limit=10,
).points
for hit in results:
    print(hit.id, hit.score, hit.payload)

L'ordre est important : créer les index de payload avant l'upsert des points. Qdrant construit les arêtes HNSW conscientes des filtres lors de la construction de l'index, à partir des index de payload existants. Il reste possible d'ajouter un index après coup et le filtrage restera fonctionnel, mais le graphe conscient des filtres est alors reconstruit intégralement, ce qui est coûteux sur une grande collection.

Recherche filtrée : la différence clé par rapport à pgvector

# continues from the setup snippet (same client and fake_embedding helper)
from qdrant_client.models import Filter, FieldCondition, MatchValue, Range

# Filter-aware HNSW: Qdrant navigates only the sub-graph matching the filter.
# pgvector post-filters (fetch N candidates, discard non-matching ones).
# When the filter is selective (<10% of corpus matches), pgvector must
# over-fetch to find enough results: latency spikes under concurrent load.
# Qdrant keeps latency proportional to the result set, not the corpus.

results = client.query_points(
    collection_name="documents",
    query=fake_embedding(),  # your real query embedding here
    query_filter=Filter(
        must=[
            FieldCondition(
                key="department",
                match=MatchValue(value="finance"),
            ),
            FieldCondition(
                key="year",
                range=Range(gte=2024, lte=2025),
            ),
        ]
    ),
    limit=10,
).points

# The same Filter works in scroll (batch retrieval by payload condition)
# and in count (cardinality estimate before the search).
count = client.count(
    collection_name="documents",
    count_filter=Filter(
        must=[FieldCondition(key="department", match=MatchValue(value="finance"))]
    ),
    exact=True,
)
print(f"finance docs: {count.count}")

Le Filter parcourt un sous-graphe de points correspondant aux conditions. Sans index de payload sur department et year, Qdrant revient au post-filtrage et se comporte comme pgvector. Le gain est maximal quand le filtre est sélectif : un filtre correspondant à 0,5 % du corpus est là où l'approche post-filtre est la plus pénalisante.

Comparatif

vs pgvector

Extension Postgres vs base vectorielle dédiée, recherche filtrée, jointures, transactions

pgvector est le défaut recommandé : il s'exécute dans Postgres, n'engendre aucune infrastructure supplémentaire et gère les jointures SQL et les écritures transactionnelles en parallèle de la recherche vectorielle. Son index HNSW post-filtre : il parcourt le graphe complet, puis applique la condition WHERE. Quand le filtre est sélectif (0,5 % du corpus), l'index doit sur-récupérer des candidats avant d'éliminer les non-correspondants, ce qui fait exploser la latence sous charge. À 5 millions de vecteurs, la latence p95 de pgvector peut atteindre 80 à 140 ms selon les paramètres ef_search. pgvector 0.8.0+ a ajouté un parcours d'index itératif qui atténue partiellement ce problème, mais les filtres composites multi-clés restent dégradés. Passer à Qdrant quand le filtrage est sélectif, le corpus grand ou un SLA p99 non négociable. Sources : qdrant.tech/documentation, markaicode.com.

vs Weaviate

Runtime, empreinte mémoire, support multi-modal

Weaviate est écrit en Go, ce qui introduit des pauses GC sous forte charge concurrente en écriture. Le runtime Rust de Qdrant n'a pas de GC. Les benchmarks (2025) montrent que Qdrant consomme 2 à 3 fois moins de mémoire que les bases vectorielles en Go pour des tailles de corpus équivalentes, ce qui compte sur des noeuds auto-hébergés à ressources limitées. Weaviate offre une API GraphQL riche et un support multi-modal de première classe (texte + image dans la même collection), ce qui en fait un meilleur choix quand le pipeline mélange les modalités. Pour un déploiement léger hébergé en Europe centré sur le RAG texte ou la mémoire d'agents, le profil de ressources de Qdrant l'emporte. Sources : blog.elest.io, zilliz.com/comparison.

vs Milvus

Limite de montée en charge, complexité opérationnelle, modèle de déploiement

Milvus (Go + C++) est conçu pour des centaines de millions à des milliards de vecteurs. Son architecture distribuée nécessite un cluster Kubernetes avec plusieurs services avec état (etcd, MinIO, noeuds de requête, noeuds de données) : une charge opérationnelle nettement plus élevée qu'un seul container Docker Qdrant. Milvus a sa place quand le corpus atteint des centaines de millions de vecteurs et que ses leviers d'indexation spécifiques (DISKANN, IVF_FLAT, IVF_PQ) et son pipeline de compaction sont nécessaires. Pour la plage 1 M à 100 M qui couvre la majorité des charges RAG et mémoire d'agents, Qdrant gère la charge à un coût opérationnel bien inférieur. Sources : kunalganglani.com, milvus.io.

Ressources

FAQ

À quel moment précis faut-il passer de pgvector à Qdrant ?
Le signal le plus clair est la sélectivité du filtrage de métadonnées : si vos requêtes combinent similarité vectorielle avec une condition qui correspond à moins de 10 à 20 % du corpus (filtre tenant, plage de dates, type de document), l'approche post-filtrage de pgvector doit sur-récupérer des candidats pour en trouver suffisamment qui passent. Le deuxième signal est la taille du corpus sous charge concurrente : au-delà de 1 à 5 millions de vecteurs, la latence p95 de pgvector augmente dans de nombreuses configurations de production. Approche pratique : déployer pgvector en premier, instrumenter la latence p95/p99 et observer si les filtres sélectifs font exploser la latence, puis basculer vers Qdrant quand une dégradation est observée sur des requêtes réelles, pas projetées.
Qdrant auto-hébergé est-il conforme au RGPD pour les déploiements en Europe ?
Qdrant Solutions GmbH est immatriculée en Allemagne. Faire tourner Qdrant sur une infrastructure exclusivement européenne (Hetzner, Scaleway) garantit que vos données vectorielles ne quittent jamais la juridiction de l'UE et évite l'exposition au CLOUD Act applicable aux hébergeurs dont le siège est aux États-Unis, quelle que soit la localisation physique des données. La conformité RGPD reste votre responsabilité en tant que responsable du traitement : une base légale pour le traitement, des contrats de sous-traitance avec les sous-traitants et des contrôles de rétention appropriés sont requis. Qdrant auto-hébergé vous donne la couche infrastructure ; les contrôles de conformité sont à votre charge. Voir le pilier sovereign-ai pour le cadre complet de juridiction des données en Europe.
En quoi le filtrage de Qdrant diffère-t-il de la clause WHERE de pgvector ?
pgvector applique les filtres WHERE après la traversée HNSW : il récupère N candidats depuis le graphe, puis écarte ceux qui ne correspondent pas. Quand le filtre est sélectif, la base doit récupérer bien plus de N candidats pour en trouver suffisamment qui correspondent, ce qui gonfle la latence et la charge CPU. Qdrant intègre des arêtes conscientes des filtres dans le graphe HNSW quand des index de payload existent au moment de la construction : une requête filtrée parcourt un sous-graphe déjà restreint aux points correspondants, de sorte que le nombre de noeuds visités est proportionnel à l'ensemble de résultats et non au corpus. L'implication pratique : créer les index de payload avant de charger les données, car Qdrant n'intègre la prise en compte des filtres dans le HNSW qu'au moment de la construction de l'index. Ajouter des index après l'ingestion déclenche une reconstruction.
Qu'est-ce que TurboQuant et faut-il l'utiliser ?
TurboQuant (Qdrant 1.18, adapté de Google Research) est une méthode de quantification par rotation. Elle applique une rotation de Hadamard aux valeurs vectorielles avant la compression en 4 bits (ou moins), ce qui distribue l'énergie de façon plus homogène entre les coordonnées et réduit la pénalité de rappel de la quantification. À 4 bits, TurboQuant offre un rappel similaire à la quantification scalaire (SQ8) pour environ 2 fois moins de mémoire. À utiliser quand le corpus est trop volumineux pour la RAM en précision float32 complète et que l'on souhaite éviter la dégradation du rappel de la quantification binaire. Commencer avec bits4 (la valeur par défaut) et mesurer le rappel sur le corpus spécifique avant de passer à bits2 ou bits1.
Peut-on faire tourner Qdrant sur le même serveur que la stack d'orchestration d'agents ?
Oui. Qdrant s'exécute comme un seul container Docker, en écoute sur le port 6333 (REST) ou 6334 (gRPC). Pour un corpus de moins de 5 millions de vecteurs, une instance à 8 Go de RAM gère à la fois le store vectoriel et un processus d'orchestration sans contention de ressources. À plus grande échelle, dédier un noeud séparé à Qdrant pour que la latence reste prévisible sous charge de recherche concurrente. Avec la quantification, les besoins en mémoire chutent significativement : 5 millions de vecteurs à 1536 dimensions en TurboQuant 4 bits tient dans environ 6 Go, ce qui rend la co-localisation viable sur un noeud dédié modeste.