Index HNSW dans Postgres : bien indexer pour la recherche vectorielle
HNSW est l’algorithme d’indexation que pgvector recommande pour la recherche vectorielle par similarité sur Postgres. Ce guide montre comment créer un index HNSW correctement réglé. Trois choix comptent : le type de colonne selon la dimension de vos embeddings, les paramètres m et ef_construction à la construction, et ef_search à chaque requête pour arbitrer recall et latence.
La recherche vectorielle native d’Aurabase (RAG, pgvector, embeddings) s’appuie sur ce même mécanisme d’indexation, décrit en détail sur la page IA native sur Postgres. Ce guide suppose une table Postgres avec pgvector déjà installé, une colonne de type vector, et au moins quelques milliers de lignes. En dessous, un simple parcours séquentiel reste souvent plus rapide qu’un index approximatif.
- HNSW ne demande aucune phase d’entraînement, contrairement à IVFFlat : l’index se construit au fil des insertions, disponible dans pgvector depuis la version 0.5.0.
- Deux paramètres fixent la qualité de l’index à la construction :
m(connexions par nœud, défaut 16) etef_construction(largeur de recherche à la construction, défaut 64). - Un troisième paramètre,
hnsw.ef_search(défaut pgvector : 40), se règle à chaque requête, sans reconstruire l’index, pour arbitrer recall et latence. - pgvector plafonne l’indexation HNSW du type
vectorà 2000 dimensions. Au-delà (un embedding à 3072 dimensions, par exemple), un cast vershalfvecest nécessaire pour indexer. - pgvector 0.8.6 est la version embarquée dans l’image Postgres tenant d’Aurabase, vérifiée directement dans le Dockerfile le 24 août 2026.
Qu’est-ce qu’un index HNSW dans pgvector ?
HNSW signifie Hierarchical Navigable Small World. C’est un index de graphe : chaque vecteur devient un nœud relié à ses voisins les plus proches, organisés en plusieurs couches superposées. Une recherche démarre au sommet du graphe, sur la couche la plus clairsemée, puis descend couche par couche vers les voisins les plus pertinents. Le temps de recherche devient ainsi quasi logarithmique, pas linéaire sur le nombre de lignes.
IVFFlat, l’autre index de pgvector, fonctionne différemment : il découpe l’espace vectoriel en listes déterminées par un passage d’entraînement sur un échantillon existant, avant de pouvoir indexer quoi que ce soit. HNSW n’a pas cette contrainte, chaque insertion enrichit directement le graphe, ce qui le rend plus simple à opérer sur une table qui grossit en continu. En contrepartie, un index HNSW consomme davantage de mémoire et prend plus de temps à construire qu’un IVFFlat équivalent sur le même volume.
pgvector introduit le support HNSW en version 0.5.0. Les versions ultérieures ajoutent des capacités utiles pour ce guide : le type halfvec (0.7.0) pour indexer au-delà de 2000 dimensions, et le paramètre hnsw.iterative_scan (0.8.0) pour améliorer le recall sur des requêtes filtrées. Si vous comparez pgvector à une base vectorielle dédiée avant de trancher, notre comparatif pgvector vs Pinecone, Weaviate et Qdrant détaille les compromis.
Vérifiez votre version de pgvector avant de créer l’index
Confirmez la version de pgvector installée avant toute chose. Une extension trop ancienne fait échouer silencieusement certaines fonctionnalités de ce guide, en particulier halfvec et hnsw.iterative_scan.
HNSW existe depuis pgvector 0.5.0. Le type halfvec, nécessaire pour indexer des embeddings au-delà de 2000 dimensions, demande au moins la version 0.7.0. Le paramètre hnsw.iterative_scan demande la version 0.8.0.
Sur les projets Aurabase, la question ne se pose pas : l’image Postgres tenant embarque pgvector 0.8.6, aussi bien sur le cluster Postgres partagé (docker/Postgres.Dockerfile, construit directement sur pgvector/pgvector:0.8.6-pg16-bookworm) que sur les instances Postgres 16 CNPG dédiées par projet (docker/Postgres.CNPG.Dockerfile, qui hérite pgvector 0.8.6 de l’image CloudNativePG officielle). Vérifié dans les deux Dockerfiles le 24 août 2026.
Choisissez le bon type de colonne selon la dimension de vos embeddings
Le type de colonne dépend de la dimension de vos embeddings, pas seulement du modèle qui les génère. pgvector stocke un vecteur classique dans le type vector, avec un plafond de 16 000 dimensions en stockage. Mais l’indexation HNSW sur ce type, elle, est limitée à 2000 dimensions : au-delà, CREATE INDEX échoue.
Les modèles d’embeddings courants dépassent souvent ce seuil : text-embedding-3-large d’OpenAI ou gemini-embedding-2 de Google produisent nativement jusqu’à 3072 dimensions. Pour indexer ces vecteurs avec HNSW, castez la colonne vers halfvec (précision de stockage réduite de moitié), ce qui repousse la limite d’indexation bien au-delà de 2000 dimensions.
Le moteur RAG d’Aurabase illustre ce compromis en production : trois classes de dimensions supportées (768, 1536, 3072), stockées dans trois colonnes distinctes de la même table embeddings. Les colonnes 768 et 1536 sont indexées directement en HNSW sur le type vector. La colonne 3072 est indexée via un cast ::halfvec(3072), précisément pour contourner le plafond des 2000 dimensions.
Pour le détail de l’ingestion (chunking, appel au fournisseur d’embeddings, insertion), voir le tutoriel pipeline RAG sur pgvector.
Créez l’index avec les paramètres m et ef_construction
La syntaxe minimale suffit pour un premier index, avec les valeurs par défaut de pgvector.
pgvector applique alors m = 16 et ef_construction = 64. Pour ajuster ces valeurs explicitement, utilisez la clause WITH :
maintenance_work_mem pour la session : c’est, selon la documentation pgvector elle-même, le levier le plus direct pour réduire le temps de construction.Que change le paramètre m ?
m fixe le nombre maximal de connexions que chaque nœud du graphe conserve par couche. Une valeur plus élevée densifie le graphe : le recall augmente, mais la mémoire consommée et le temps de construction augmentent aussi, à peu près linéairement. La valeur par défaut (16) convient à la plupart des cas. Monter à 24 ou 32 se justifie surtout sur des embeddings de grande dimension, où la distinction entre voisins proches et lointains devient plus fine.
Que change ef_construction ?
ef_construction fixe la taille de la liste de candidats explorée pendant la construction de l’index, pour chaque nœud inséré. Une valeur plus élevée améliore la qualité du graphe final, donc le recall potentiel, au prix d’un temps de construction plus long. Contrairement à m, ce paramètre n’a aucun coût au moment de la requête : c’est un investissement ponctuel, payé une seule fois à la création de l’index.
Index partiels pour plusieurs classes de dimensions dans une même table
Quand une table stocke plusieurs colonnes vectorielles (une par classe de dimension, comme le fait Aurabase), indexez chaque colonne séparément avec une clause WHERE colonne IS NOT NULL. Cet index partiel évite d’indexer des lignes vides pour les classes non utilisées par une ligne donnée, ce qui réduit la taille de l’index et accélère sa construction sans rien coûter en recall.
Le choix de la classe d’opérateurs (vector_cosine_ops, vector_l2_ops ou vector_ip_ops) doit correspondre à la métrique sur laquelle le modèle d’embedding a été entraîné. La plupart des modèles de text embeddings récents sont entraînés pour la similarité cosinus : vector_cosine_ops (ou halfvec_cosine_ops sur une colonne castée) est donc le choix par défaut le plus sûr.
Réglez ef_search au moment de la requête
ef_search se règle à chaque requête, pas à la construction de l’index. Il fixe la taille de la liste de candidats explorée pendant la recherche : plus il est élevé, meilleur est le recall, au prix d’une latence plus longue. pgvector fixe sa valeur par défaut à 40.
40 suffit rarement dès qu’une requête combine la recherche vectorielle avec un filtre WHERE appliqué après le parcours de l’index (sur un namespace, un tenant, ou tout autre critère de métadonnées). Le parcours HNSW ramène ef_search candidats bruts, puis le filtre en écarte une partie. Si trop peu de candidats survivent, le LIMIT final se retrouve sous-rempli.
Le moteur RAG d’Aurabase élargit donc ef_search dynamiquement selon le top_k demandé, au lieu de garder la valeur fixe de 40 : ef = max(top_k × 4, 64). Une recherche des 5 résultats les plus proches utilise ef_search = 64 ; une recherche des 50 meilleurs utilise ef_search = 200. Cette formule reste ajustable par variable d’environnement pour les déploiements qui ont besoin d’un autre compromis recall/latence.
pgvector 0.8 ajoute un second levier pour ce même problème : hnsw.iterative_scan. En mode strict_order ou relaxed_order, le parcours élargit progressivement sa recherche jusqu’à réunir assez de résultats après filtre, plutôt que de s’arrêter sur une liste de candidats figée. Aurabase l’active par défaut en strict_order, mais protège l’appel dans un savepoint. Sur une version de pgvector antérieure à 0.8, où ce paramètre n’existe pas, la requête continue en mode dégradé plutôt que d’échouer.
Construire le pipeline RAG complet
Cet index HNSW n’est qu’une pièce du pipeline RAG complet : chunking, génération des embeddings, ingestion, puis recherche. Notre tutoriel pas-à-pas construit ce pipeline de bout en bout sur pgvector, de la première insertion jusqu’à la requête de similarité. La documentation technique détaille par ailleurs l’ensemble des capacités IA natives d’Aurabase construites sur Postgres.