SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor

Base vectorielle sur VPS : pgvector, Qdrant ou Chroma ?

Votre application et votre index partagent le VPS : comparez pgvector, Qdrant, Chroma et la recherche brute, puis calculez la RAM nécessaire.

Ce que vous coûte réellement une base de données vectorielle sur un VPS

Exécuter une base de données vectorielle sur un VPS (serveur privé virtuel) supprime le problème que chaque fournisseur managed propose de résoudre. Votre application et votre index se trouvent sur la même machine. Une requête de recherche passe donc par une socket loopback, et non par le réseau. Il reste le coût qui a toujours été le véritable coût : transformer le texte en vecteurs. Deux autres coûts s’y ajoutent : le temps nécessaire pour construire l’index et la RAM qu’il occupe tant qu’il est utilisé.

Cela change les décisions importantes. La région et le temps aller-retour vers l’endpoint ne sont plus votre problème. En revanche, vous devez tenir compte du produit du nombre de vecteurs, de leur dimension et de quatre octets. Ce produit détermine si l’index tient dans la mémoire que vous louez chaque mois.

Où passent réellement les millisecondes sur une seule machine

Suivez une requête de similarité à travers une stack auto-hébergée.

  1. Le texte de la requête est transformé en vecteur par un modèle d’embedding. Sur CPU, cela prend de quelques dizaines à quelques centaines de millisecondes pour une chaîne courte. Sur GPU, le temps se compte en unités de millisecondes.
  2. Le vecteur est envoyé au store. Sur une connexion TCP loopback ou une socket Unix, cela prend une fraction de milliseconde.
  3. Le store parcourt son index et renvoie les lignes les plus proches.
  4. Votre code lit le texte correspondant et construit un prompt.

L’étape 1 est généralement la plus longue de cette liste. L’étape 2 est celle sur laquelle les fournisseurs hébergés se livrent concurrence, mais sur une seule machine, elle est presque inexistante. Ne devinez pas la répartition. Mesurez les deux extrémités sur votre propre serveur.

curl http://127.0.0.1:11434/api/embed -s -o /dev/null \
  -w 'embed: %{time_total}s\n' \
  -d '{"model": "nomic-embed-text", "input": "how do I rotate the api key"}'

Exécutez ensuite \timing on dans psql avant la requête de recherche. Si la première commande affiche embed: 0.184312s et que psql renvoie Time: 4.201 ms, optimiser l’index n’est pas la bonne tâche : votre latence vient du modèle d’embedding. Exécuter le modèle d’embedding localement avec Ollama place l’étape 1 sur le même CPU que les étapes 2 et 3. Les deux moitiés se disputent donc les mêmes cœurs et la même RAM. La boucle d’ingestion et de retrieval qui entoure ce store est décrite dans le guide du pipeline RAG auto-hébergé. RAG signifie retrieval augmented generation : vous recherchez vos propres documents, puis vous insérez les meilleures correspondances dans un prompt.

En dessous d’environ cent mille vecteurs, parcourez-les tous

Un scan exhaustif compare la requête à chaque vecteur stocké. Le rappel est parfait par définition. Aucun index ni étape de build n’est nécessaire, et les résultats ne peuvent pas devenir obsolètes par rapport à vos données.

Le calcul indique à partir de quand cette approche cesse d’être adaptée. Un scan lit n * d * 4 octets par requête, où n est le nombre de vecteurs et d leur dimension. Avec 100,000 vecteurs de 768 dimensions, cela représente 307 MB par requête, ce qu’un CPU moderne parcourt en quelques dizaines de millisecondes. Avec 5 millions de vecteurs, cela représente 15 GB par requête : ce n’est plus vraiment une requête.

Stockez donc les vecteurs dans SQLite et effectuez la comparaison dans NumPy.

import sqlite3, numpy as np

db = sqlite3.connect("docs.db")
db.execute("CREATE TABLE IF NOT EXISTS docs (id INTEGER PRIMARY KEY, body TEXT, vec BLOB)")

def add(body, vec):
    v = np.asarray(vec, dtype=np.float32)
    v /= np.linalg.norm(v)
    db.execute("INSERT INTO docs (body, vec) VALUES (?, ?)", (body, v.tobytes()))
    db.commit()

def search(query_vec, k=5):
    rows = db.execute("SELECT id, body, vec FROM docs").fetchall()
    mat = np.frombuffer(b"".join(r[2] for r in rows), dtype=np.float32).reshape(len(rows), -1)
    q = np.asarray(query_vec, dtype=np.float32)
    q /= np.linalg.norm(q)
    scores = mat @ q
    return [(rows[i][0], rows[i][1], float(scores[i])) for i in np.argsort(-scores)[:k]]

Les deux côtés sont normalisés à une longueur unitaire. Le produit scalaire est donc la similarité cosinus, et un score élevé indique une correspondance plus proche. Chargez mat une seule fois au démarrage, plutôt qu’une fois par requête. La lecture SQLite sort ainsi entièrement du chemin critique.

Mesurez les performances sur votre propre machine avant de rejeter cette approche.

import time
t = time.perf_counter()
search(q)
print(f"{(time.perf_counter() - t) * 1000:.1f} ms")

Les limites réelles sont les suivantes : un seul processus conserve la matrice complète en RAM, et aucun filtrage par métadonnées ni gestion des écritures concurrentes n’est prévu. Si l’un de ces points vous pose problème, changez d’approche. SQLite est lui-même un datastore sérieux côté serveur, comme l’explique le guide SQLite en production. Si votre charge consiste réellement à parcourir des colonnes plutôt qu’à servir des lignes, la comparaison entre DuckDB et SQLite sera plus utile.

pgvector, si vous utilisez déjà Postgres

Si votre application dispose déjà d’une base de données Postgres, pgvector ajoute le moins de surface technique possible. Il s’agit d’une extension, pas d’un service. Les vecteurs sont stockés dans une table normale, à côté de la ligne qu’ils décrivent. Une recherche filtrée utilise donc une clause WHERE au lieu d’un second système à maintenir en synchronisation.

Ubuntu 24.04 l’inclut dans le composant universe.

sudo apt update
sudo apt install -y postgresql-16-pgvector
sudo -u postgres psql -d yourdb -c 'CREATE EXTENSION vector;'

En août 2026, ce paquet correspond à pgvector 0.6.0, une version très en retard sur l’amont. Les index scans itératifs nécessitent notamment la version 0.8. Utilisez donc le dépôt officiel du projet PostgreSQL.

sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
sudo apt install -y postgresql-17-pgvector

Remplacez 17 par la version majeure de votre serveur, affichée par sudo -u postgres psql -tAc 'SHOW server_version'. Si vous installez le paquet d’extension compilé pour une mauvaise version majeure, CREATE EXTENSION échoue, car Postgres recherche uniquement dans le répertoire share de la version en cours d’exécution :

ERROR:  could not open extension control file "/usr/share/postgresql/16/extension/vector.control": No such file or directory

Le schéma utilise du SQL ordinaire avec un nouveau type.

CREATE TABLE chunks (
  id        bigserial PRIMARY KEY,
  doc_id    bigint NOT NULL,
  body      text   NOT NULL,
  embedding vector(768)
);

SELECT id, body FROM chunks
ORDER BY embedding <=> '[0.013, -0.021, 0.004]'
LIMIT 5;

<=> correspond à la distance cosinus, <-> à la distance L2 (euclidienne) et <#> au produit scalaire interne négatif. Utilisez celle pour laquelle votre modèle d’embeddings a été entraîné. Si vous choisissez la mauvaise, aucune erreur ne se produit, mais vos résultats sont simplement moins bons sans que cela soit signalé.

Sans index, cette requête effectue une recherche exacte sur chaque ligne. Il s’agit de l’équivalent Postgres de la recherche brute présentée plus haut, avec le même rappel parfait. Augmenter max_parallel_workers_per_gather permet d’utiliser davantage de cœurs. Commencez par cette méthode, puis ajoutez l’index. Vous disposerez ainsi d’une référence de rappel pour mesurer l’index.

Qdrant, lorsque l’index dépasse les capacités de la base de données

Qdrant est un vector store dédié écrit en Rust. Il devient pertinent lorsque l’index est suffisamment volumineux pour que vous préfériez que sa construction ne concurrence pas l’instance Postgres de votre application, ou lorsque vous avez besoin du filtrage des payloads et de la quantification que pgvector ne propose pas.

docker run -d --name qdrant \
  -p 127.0.0.1:6333:6333 -p 127.0.0.1:6334:6334 \
  -e QDRANT__SERVICE__API_KEY="$(openssl rand -hex 32)" \
  -v "$(pwd)/qdrant_storage:/qdrant/storage:z" \
  qdrant/qdrant

Le port 6333 sert l’API REST (representational state transfer) et un dashboard à l’adresse /dashboard, tandis que le port 6334 sert gRPC. Deux points sont importants sur un VPS public. La documentation de Qdrant précise que le service s’exécute par défaut « sans chiffrement ni authentification », et le -p 6333:6333 du quickstart est lié à toutes les interfaces. Docker le publie ainsi malgré une règle ufw, car il écrit ses propres règles de forwarding. Liez le service à 127.0.0.1 et définissez une clé API. Une instance Qdrant accessible depuis une IP publique sans clé constitue une copie publique de vos documents.

curl -s http://127.0.0.1:6333/collections -H "api-key: $QDRANT_API_KEY"

Une réponse saine ressemble à {"result":{"collections":[]},"status":"ok","time":0.00002}. Si {"status":{"error":"Unauthorized"}} est renvoyé, le nom de l’en-tête ou la clé est incorrect. Si rien n’est renvoyé, le conteneur ne s’exécute pas ou est lié ailleurs. La question de savoir si ce conteneur est adapté à votre machine relève de la gestion habituelle d’un service stateful. La comparaison entre une base de données Docker et une base de données installée sur l’hôte s’applique donc ici sans modification.

Chroma et son utilité

Chroma est le moyen le plus rapide de passer de rien à une démonstration fonctionnelle de retrieval.

pip install chromadb
chroma run --path /srv/chroma

Ce serveur écoute sur le port 8000, et chromadb.HttpClient(host="localhost", port=8000) s’y connecte. Chroma fournit une fonction d’embedding par défaut. Un premier prototype n’a donc pas besoin d’un serveur de modèles séparé.

Soyez clair sur le compromis. Chroma est agréable à utiliser parce qu’il masque les décisions abordées dans ce guide : la métrique de distance et la quantité de RAM nécessaire pour stocker le résultat. C’est adapté à un prototype, mais pas au système dont vous devrez traiter les alertes. Si vos données résident déjà dans Postgres, les déplacer vers Chroma ajoute un processus et un problème de synchronisation pour résoudre un problème que pgvector ne pose pas.

De quelle quantité de RAM l’index a-t-il besoin ?

Commencez par les vecteurs bruts, car ils constituent le minimum incompressible et aucun réglage ne peut le réduire.

bytes = number_of_vectors * dimensions * 4

Quatre octets correspondent à un flottant 32 bits par dimension. La documentation de capacity planning de Qdrant applique un coefficient de 1.5 pour les métadonnées et les segments temporaires créés pendant l’optimisation :

memory_size = number_of_vectors * vector_dimension * 4 bytes * 1.5

Voici cette formule appliquée à un million de vecteurs, avec les dimensions produites par les modèles d’embedding courants.

ChartRAM for 1 million vectors, by embedding dimension
The data behind this chart
[
  {
    "label": "384 dims",
    "raw_gib": 1.43,
    "with_overhead_gib": 2.15
  },
  {
    "label": "768 dims",
    "raw_gib": 2.86,
    "with_overhead_gib": 4.29
  },
  {
    "label": "1024 dims",
    "raw_gib": 3.81,
    "with_overhead_gib": 5.72
  },
  {
    "label": "1536 dims",
    "raw_gib": 5.72,
    "with_overhead_gib": 8.58
  },
  {
    "label": "3072 dims",
    "raw_gib": 11.44,
    "with_overhead_gib": 17.17
  }
]

Il s’agit du résultat de la formule en gibioctets (GiB), pas d’une mesure. Interprétez ces valeurs comme la quantité de mémoire à réserver. Un modèle de 768 dimensions, tel que nomic-embed-text, appliqué à un million de chunks, nécessite environ 4.29 GiB. Cela tient dans une offre de 8 Go, avec encore de la marge pour Postgres. Le même corpus en 3072 dimensions nécessite 17.17 GiB et ne tient pas dans cette offre.

Le levier se trouve dans la première colonne de ce tableau, pas dans la dernière. Diviser la dimension par deux divise par deux chaque octet qui en dépend, de manière permanente. Sur un VPS, un modèle de 768 dimensions qui obtient un score légèrement inférieur sur un leaderboard public constitue souvent un meilleur choix technique. Le type halfvec de pgvector stocke ensuite des flottants 16 bits, ce qui divise encore la taille par deux, et l’indexation se fait au moyen d’une expression :

CREATE INDEX ON chunks USING hnsw ((embedding::halfvec(768)) halfvec_cosine_ops);

Vous devez connaître une limite avant de choisir un modèle. Le type vector de pgvector accepte jusqu’à 16,000 dimensions, mais ses index HNSW et IVFFlat ne couvrent que 2,000 dimensions. Au-delà, vous pouvez indexer un cast vers halfvec, qui prend en charge jusqu’à 4,000 dimensions, ou ne pas créer d’index.

Coût de m et de ef_construction lors de la construction

HNSW (hierarchical navigable small world) est l’index utilisé par pgvector et Qdrant. Il s’agit d’un graphe en couches. Chaque vecteur est un nœud relié à des nœuds proches. Lors d’une recherche, le parcours suit ces liens vers la requête au lieu de tout lire.

m correspond au nombre de liens conservés par chaque nœud. La documentation de Faiss indique que la mémoire HNSW est de (d * 4 + m * 2 * 4) octets par vecteur et recommande une valeur de m comprise entre 4 et 64. Prenons le cas de 768 dimensions et d’un million de vecteurs.

ChartCost of raising m at 768 dimensions, 1 million vectors
The data behind this chart
[
  {
    "label": "m = 8",
    "link_bytes_per_vector": 64,
    "total_gib": 2.92
  },
  {
    "label": "m = 16 (default)",
    "link_bytes_per_vector": 128,
    "total_gib": 2.98
  },
  {
    "label": "m = 32",
    "link_bytes_per_vector": 256,
    "total_gib": 3.1
  },
  {
    "label": "m = 64",
    "link_bytes_per_vector": 512,
    "total_gib": 3.34
  }
]

La différence de taille est particulièrement notable. Passer de la valeur par défaut m = 16 à m = 64 ajoute 512 octets de liens par vecteur, contre 3072 octets de données de vecteur. La taille totale passe donc de 2.98 Gio à 3.34 Gio. Cela représente environ 12 %. Avec ces dimensions, m n’est pas le principal poste de consommation mémoire. Ce sont les vecteurs qui occupent l’essentiel de la mémoire.

Le véritable coût de m concerne le temps de construction et le temps d’insertion. Pour placer un nœud, il faut trouver et relier ce nombre de voisins. ef_construction définit la taille de la liste de candidats examinée par le constructeur lors du placement de chaque nœud. Augmenter cette valeur produit un meilleur graphe, mais ralentit la construction. Cela ne modifie pas du tout la taille de l’index terminé.

SET maintenance_work_mem = '4GB';
SET max_parallel_maintenance_workers = 7;
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

maintenance_work_mem est le paramètre qui détermine si la construction prend quelques minutes ou plusieurs heures, car pgvector assemble le graphe en mémoire lorsque celui-ci y tient. Lorsqu’il n’y tient plus, pgvector l’indique :

NOTICE:  hnsw graph no longer fits into maintenance_work_mem after 100000 tuples
DETAIL:  Building will take significantly more time.
HINT:  Increase maintenance_work_mem to speed up builds.

Ce message est la ligne la plus utile affichée par pgvector. Il signifie que la construction utilise un chemin beaucoup plus lent. Annulez-la, augmentez le paramètre au-delà de la quantité de RAM calculée plus haut, puis recommencez. Suivez la progression depuis une seconde session :

SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS pct
FROM pg_stat_progress_create_index;

HNSW indique initializing, puis loading tuples. Une construction qui reste longtemps à un faible pourcentage sur un disque peu sollicité indique le problème maintenance_work_mem, et non une requête bloquée.

Deux faits doivent guider la planification. Le README de pgvector indique que HNSW « has slower build times and uses more memory » qu’IVFFlat, mais qu’il offre en contrepartie un meilleur compromis entre vitesse et rappel. HNSW peut aussi être créé sur une table vide, tandis qu’IVFFlat doit d’abord exécuter k-means sur des données représentatives. Le construire sur une table vide donne donc un mauvais rappel. Dans un schéma vierge, HNSW est l’index que vous pouvez créer à l’avance.

ef_search : le paramètre à régler après la création de l’index

m et ef_construction sont figés dans l’index. ef_search ne l’est pas. Il définit le nombre de candidats conservés par la recherche pendant le parcours du graphe. Vous pouvez le modifier pour chaque session ou chaque requête.

SET hnsw.ef_search = 100;
SELECT id, body FROM chunks ORDER BY embedding <=> $1 LIMIT 5;

La valeur par défaut est 40. Augmentez-la pour améliorer le rappel, au prix d’une latence plus élevée. Diminuez-la pour réduire les deux. C’est le seul paramètre de rappel que vous pouvez modifier sans recréer l’index. Réglez-le donc à partir d’un ensemble fixe de requêtes dont vous connaissez déjà les réponses correctes. Arrêtez-vous lorsque le rappel n’augmente plus.

Un piège existe. ef_search interagit mal avec une clause WHERE sélective, car l’index renvoie un nombre fixe de candidats et le filtre est appliqué ensuite. Un filtre qui rejette la plupart des lignes peut laisser moins de LIMIT résultats, alors que des lignes correspondantes se trouvent dans la table. pgvector 0.8 répond à ce problème avec les recherches itératives :

SET hnsw.iterative_scan = relaxed_order;

L’index est alors parcouru de nouveau pour récupérer davantage de candidats jusqu’à ce que la limite soit atteinte, dans la limite de hnsw.max_scan_tuples, dont la valeur par défaut est 20000. strict_order conserve l’ordre exact des distances, mais coûte davantage en ressources. C’est la fonctionnalité absente du paquet Ubuntu 0.6.0. Des lignes manquantes lorsqu’un filtre est appliqué permettent de le constater.

Pourquoi l’index doit tenir en RAM

Une recherche HNSW parcourt un graphe. Chaque saut lit un nœud stocké à un emplacement sans rapport avec le précédent. Le schéma d’accès est donc presque aléatoire et la lecture anticipée n’aide pas. Tant que le graphe reste en RAM, chaque saut correspond à une référence mémoire. Dès qu’il n’y tient plus, un saut peut devenir une lecture disque. Une recherche qui consulte quelques centaines de nœuds peut alors entraîner quelques centaines de lectures.

La documentation de Qdrant donne un ordre de grandeur : « si vous stockez deux fois moins de vecteurs en RAM, la latence de recherche double environ ». Dimensionnez votre système en conséquence.

Lorsque l’index ne tiendra réellement pas en mémoire, chaque solution implique un compromis à choisir délibérément.

  • Mappez les vecteurs en mémoire afin que le système d’exploitation mette en cache les pages fréquemment utilisées et conserve les autres sur disque. Cette approche nécessite un stockage NVMe (non-volatile memory express) rapide pour rester acceptable.
  • Quantifiez les vecteurs en stockant chaque dimension sur un octet au lieu de quatre. Le volume des vecteurs est ainsi divisé par quatre, au prix d’une perte de rappel faible mais mesurable.
  • Convertissez les vecteurs en halfvec avec pgvector. Le volume est réduit de moitié, avec une perte de rappel inférieure à celle d’une quantification sur un octet.
  • Utilisez un modèle plus petit pour générer les embeddings. C’est la correction la moins coûteuse, mais elle est souvent écartée, car elle impose de regénérer les embeddings du corpus.

Modes d’échec et messages affichés

could not open extension control file. Le paquet pgvector correspondant à la version majeure de Postgres en cours d’exécution n’est pas installé. Affichez la version avec sudo -u postgres psql -tAc 'SHOW server_version', puis installez le paquet correspondant postgresql-NN-pgvector.

ERROR: expected 768 dimensions, not 1536. Le type de colonne et le modèle ne correspondent pas. Vous avez changé de modèle d’embedding sans recalculer les embeddings. Il n’existe pas de correction partielle dans ce cas, car les vecteurs produits par deux modèles différents ne sont pas du tout comparables. Toutes les lignes doivent donc être régénérées.

La requête est lente et EXPLAIN affiche un parcours séquentiel. La classe d’opérateurs de l’index et l’opérateur de la requête ne correspondent pas. vector_cosine_ops ne prend en charge que <=>. Exécutez EXPLAIN ANALYZE sur la requête et recherchez Index Scan using ... on chunks. Si vous voyez plutôt Seq Scan on chunks, recréez l’index avec la classe d’opérateurs correspondant à l’opérateur réellement utilisé dans vos requêtes.

Moins de lignes que LIMIT, avec une clause WHERE présente. C’est le piège du filtrage décrit plus haut. Augmentez hnsw.ef_search, ou passez à pgvector 0.8 et définissez hnsw.iterative_scan.

La création de l’index se termine avec un processus disparu et aucune erreur dans psql. Si maintenance_work_mem est défini sur la majeure partie de la mémoire de la machine, tandis que shared_buffers et votre application en consomment également, le processus finit tué par l’outil de gestion des processus hors mémoire du noyau. sudo dmesg -T | grep -i 'killed process' affiche la ligne qui nomme postgres. Réduisez ce paramètre, ou créez l’index sur une offre disposant de davantage de mémoire, puis restaurez le dump.

Choisir une solution

Si vous utilisez déjà Postgres et avez moins de quelques millions de vecteurs, utilisez pgvector. L’index se trouve à côté des données, le filtrage s’exprime avec une clause WHERE, et vos sauvegardes existantes l’incluent déjà. Si l’index est suffisamment volumineux pour nécessiter sa propre limite de mémoire, ou si vous avez besoin d’un filtrage important des payloads, exécutez Qdrant à côté de Postgres et acceptez de gérer un second service.

En dessous d’environ 100000 vecteurs, mesurez le scan exhaustif avant d’installer quoi que ce soit. À cette échelle, une recherche exhaustive avec un rappel parfait et sans étape de construction n’est pas un compromis. C’est la bonne solution. Utiliser plutôt un index approximatif revient à ajouter du tuning et une pression sur la RAM en échange de quelques millisecondes que vous ne dépensiez pas.

FAQ

Ai-je besoin d’une base de données vectorielle dédiée, ou Postgres suffit-il ?

Si vos données sont déjà dans Postgres, pgvector suffit bien plus longtemps que ne le laissent penser la plupart des comparatifs. Il stocke les vecteurs dans une colonne ordinaire. Une recherche filtrée utilise donc une clause WHERE, et vos sauvegardes existantes couvrent l’index. Passez à un moteur dédié comme Qdrant lorsque la charge vectorielle doit disposer de sa propre limite mémoire, ou lorsque vous avez besoin du filtrage des payloads et de la quantification que pgvector ne fournit pas.

Combien de vecteurs un VPS peut-il contenir ?

Calculez-le au lieu de l’estimer, avec number_of_vectors * dimensions * 4 bytes * 1.5. Un million de vecteurs de 768 dimensions représentent environ 4.3 GiB. Une offre de 8 GB peut donc les contenir tout en laissant de la mémoire à Postgres. Un million de vecteurs de 3072 dimensions représentent environ 17 GiB et nécessitent une offre beaucoup plus grande. Le facteur qui influe le plus est la dimension de votre modèle d’embedding. Choisissez donc ce modèle en tenant compte de son coût mémoire.

Pourquoi ma recherche vectorielle est-elle lente lorsque l’index se trouve sur la même machine ?

Le réseau n’est pas en cause sur une seule machine. Examinez donc les deux éléments qui peuvent l’être. Commencez par mesurer séparément la durée de l’appel d’embedding. La génération du vecteur de requête sur le CPU prend souvent beaucoup plus de temps que la recherche elle-même. Vérifiez ensuite que l’index tient en RAM. Une recherche HNSW parcourt aléatoirement un graphe. Lorsque le graphe déborde sur le disque, chaque étape peut devenir une lecture disque. Selon les recommandations de Qdrant, réduire de moitié le nombre de vecteurs conservés en RAM double environ la latence de recherche.

Dois-je créer un index HNSW ?

Pas en dessous d’environ cent mille vecteurs. Un scan exhaustif lit n * d * 4 octets par requête. Cela représente 307 MB pour 100,000 vecteurs de 768 dimensions. Un CPU moderne parcourt ce volume en quelques dizaines de millisecondes, avec un rappel parfait et sans étape de création d’index. Mesurez d’abord le scan sur votre propre matériel. Créez l’index lorsque le temps mesuré du scan devient réellement trop élevé, et non parce qu’un article de benchmark le recommande.

Quel est le coût réel d’une augmentation de m ?

Le coût concerne surtout le temps de création et le temps d’insertion, bien plus que la mémoire. Avec 768 dimensions, passer de la valeur par défaut m = 16 à m = 64 ajoute 512 octets de liens dans le graphe par vecteur, contre 3072 octets pour les données du vecteur. La mémoire totale augmente donc d’environ 12 %. En revanche, chaque insertion doit rechercher et relier quatre fois plus de voisins. Ajustez d’abord ef_search, car sa modification ne coûte rien et ne nécessite pas de recréer l’index.

#vector-database#rag#pgvector#qdrant#auto-hébergement