FlowKit

pgvector : HNSW ou IVFFlat ? Optimiser la recherche vectorielle de votre RAG n8n

Publié le 25 juillet 2026 · 6 min de lecture

Votre RAG n8n répond bien, mais chaque question met plusieurs secondes à interroger Supabase ? Le suspect habituel n'est ni le modèle d'embedding ni le LLM, mais la table pgvector elle-même : sans index, chaque recherche compare le vecteur de la question à toutes les lignes de la table. Tant que la base contient quelques milliers de chunks, personne ne le remarque ; à cent mille, chaque requête devient un scan complet. pgvector propose deux types d'index approximatifs, IVFFlat et HNSW, avec des compromis différents entre vitesse de construction, mémoire, latence et qualité des résultats. Cet article les compare concrètement, dans le contexte d'un pipeline RAG n8n comme celui de notre guide RAG avec Supabase.

Sans index : la recherche exacte et ses limites

Par défaut, une requête de similarité sur une colonne vector déclenche un scan séquentiel exact : PostgreSQL calcule la distance entre le vecteur de la requête et chaque ligne, puis trie. Deux conséquences :

  • C'est parfait en qualité : les k plus proches voisins renvoyés sont, par définition, les vrais k plus proches voisins.
  • C'est linéaire en coût : le temps de requête croît avec le nombre de lignes et la dimension des vecteurs (1536 dimensions pour les embeddings couramment utilisés dans les RAG).

En pratique, jusqu'à quelques milliers de chunks — un RAG documentaire de démarrage alimenté par notre workflow d'ingestion PDF vers Supabase pgvector — le scan exact reste sous des latences confortables et il n'y a aucune raison de créer un index. C'est au-delà, quand la table grossit et que la latence de la recherche devient perceptible dans le temps de réponse global du chatbot, que la recherche approximative (ANN, approximate nearest neighbor) entre en jeu.

IVFFlat : partitionner pour chercher moins

IVFFlat découpe l'espace vectoriel en listes (des clusters calculés par k-means sur les données existantes). Au moment de la requête, l'index identifie les listes dont le centroïde est le plus proche du vecteur de la question, et ne compare qu'avec les vecteurs de ces listes-là. Cette famille d'approches par partition descend des travaux fondateurs sur la quantification, formalisés par une étude de 2011 publiée dans IEEE TPAMI (Jégou, Douze & Schmid, « Product Quantization for Nearest Neighbor Search » — Google Scholar) — IVFFlat en est un descendant simplifié : il partitionne mais stocke les vecteurs « à plat », sans compression.

Deux paramètres pilotent le compromis :

  • lists (à la création) : le nombre de partitions. Plus de listes = des partitions plus fines, donc des requêtes plus rapides, mais un risque accru de manquer des voisins situés juste de l'autre côté d'une frontière de cluster.
  • probes (à la requête, via SET ivfflat.probes = …) : le nombre de listes explorées. Augmenter probes améliore le rappel et ralentit la requête ; à l'extrême, sonder toutes les listes revient à un scan exact.

Le point faible structurel d'IVFFlat : les centroïdes sont calculés au moment de la création de l'index, à partir des données présentes. Deux implications directes pour un RAG :

  • créer l'index sur une table vide ou quasi vide n'a pas de sens — il faut d'abord ingérer un volume représentatif, puis créer l'index ;
  • si le corpus change fortement (nouvelle collection de documents très différente), les centroïdes deviennent moins représentatifs et le rappel se dégrade : il faut alors recréer l'index.

En contrepartie, IVFFlat se construit vite et occupe moins de mémoire que HNSW — un vrai avantage sur des instances Supabase modestes.

HNSW : un graphe multi-couches

HNSW (Hierarchical Navigable Small World) construit un graphe de proximité multi-couches : les couches hautes, clairsemées, permettent de longues traversées rapides ; les couches basses, denses, affinent la recherche localement. Une requête part du sommet et descend de couche en couche en se rapprochant gloutonnement du vecteur cible. L'algorithme vient d'une étude publiée dans IEEE TPAMI (Malkov & Yashunin, 2018, « Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs » — Google Scholar), qui montre notamment que cette structure hiérarchique offre un excellent compromis rappel-latence, robuste y compris sur des données en haute dimension.

Trois paramètres à connaître :

  • m (à la création) : le nombre maximal de connexions par nœud dans le graphe. Plus élevé = meilleur rappel, mais index plus gros et construction plus lente.
  • ef_construction (à la création) : la taille de la liste de candidats explorée pendant la construction. Plus élevé = graphe de meilleure qualité, construction plus longue.
  • ef_search (à la requête, via SET hnsw.ef_search = …) : la taille de la liste de candidats à l'interrogation. C'est le bouton principal du compromis rappel-latence en production.

Les forces de HNSW pour un RAG : pas besoin de données au moment de la création (l'index se construit incrémentalement au fil des insertions, ce qui colle bien à une ingestion continue), et un rappel généralement supérieur à IVFFlat à latence égale. Ses coûts : une construction nettement plus lente sur un gros corpus existant et une consommation mémoire plus élevée, à anticiper dans le dimensionnement de l'instance.

Recall vs latence : accepter l'approximation

Le point commun des deux index — et la surprise classique en passant du scan exact à l'ANN : les résultats peuvent différer de la recherche exacte. Un chunk pertinent peut sortir du top-k parce qu'il se trouvait dans une liste non sondée (IVFFlat) ou hors du chemin exploré dans le graphe (HNSW). Pour un RAG, cela se traduit par un passage manquant dans le contexte, donc potentiellement une réponse moins bonne — d'où l'intérêt de mesurer, pas seulement de chronométrer :

  • constituez un petit jeu de questions de référence dont vous connaissez les passages attendus ;
  • comparez les résultats indexés au scan exact sur ce jeu ;
  • ajustez probes ou ef_search jusqu'au rappel acceptable, en surveillant la latence.

Recommandations pratiques pour un RAG n8n + Supabase

  • Commencez sans index. Sous quelques milliers de chunks, le scan exact est rapide et parfait en qualité.
  • Choisissez HNSW par défaut quand l'index devient nécessaire : pas de contrainte de données préexistantes, meilleur compromis rappel-latence, et l'ingestion continue d'un RAG (nouveaux documents chaque semaine) ne dégrade pas sa structure comme elle dérègle les centroïdes d'IVFFlat. Réservez IVFFlat aux cas où la mémoire est contrainte et le corpus stable.
  • Restez cohérent sur l'opérateur. Pour des embeddings de LLM, la distance cosinus (<=>) est le standard ; l'index doit être créé avec la classe d'opérateurs cosinus correspondante, sinon PostgreSQL ne l'utilisera pas pour vos requêtes.
  • Fixez la dimension à la création de la table (vector(1536) par exemple, selon votre modèle d'embedding) et n'en changez plus : mélanger des embeddings de modèles différents dans une même table rend les distances incomparables. Si vous changez de modèle d'embedding, réingérez tout et recréez l'index.
  • Créez l'index en heures creuses sur un gros corpus (la construction HNSW est gourmande), et pensez à la possibilité de le construire de façon non bloquante pour ne pas geler les écritures pendant l'opération.

Où ça se branche dans n8n

Dans un workflow n8n, tout cela reste invisible au niveau des nodes : le node Supabase Vector Store (utilisé en mode insertion pour l'ingestion, en mode retrieval pour la question-réponse) interroge la table pgvector via une fonction de similarité côté Supabase — c'est cette requête SQL sous-jacente qui bénéficie de l'index, sans modification du workflow. Concrètement :

Un dernier point de vigilance : un index rapide fait chuter la latence de la recherche, pas celle des appels au modèle d'embedding — si votre ingestion massive se heurte à des erreurs 429, le problème se traite côté rate limiting des API IA, pas côté pgvector.

Le Pack Assistant RAG (119 €) livre précisément cette chaîne — ingestion, table pgvector, API de question-réponse et chatbot avec citations — en JSON importable : la structure de la table et les requêtes de similarité sont déjà cohérentes, il ne reste qu'à choisir le bon index quand votre corpus le justifie.

FAQ

Questions fréquentes

Faut-il un index pgvector dès le début de mon projet RAG ?

Non. Sans index, pgvector effectue un scan séquentiel exact : chaque requête compare le vecteur de la question à toutes les lignes de la table. Jusqu'à quelques milliers de documents, cette recherche exacte reste rapide et renvoie par définition les meilleurs résultats possibles. L'index devient utile quand la latence des requêtes se dégrade avec la croissance de la table — typiquement à partir de dizaines de milliers de lignes.

Quelle est la différence principale entre IVFFlat et HNSW ?

IVFFlat partitionne les vecteurs en listes (clusters) et ne parcourt au moment de la requête que les listes les plus proches : construction rapide, index compact, mais rappel sensible au paramétrage et à la dérive des données. HNSW construit un graphe de proximité multi-couches parcouru de haut en bas : meilleur compromis rappel-latence et pas besoin de données préexistantes, au prix d'une construction plus lente et d'une consommation mémoire supérieure.

Pourquoi mes résultats changent-ils après la création d'un index ?

Parce que IVFFlat et HNSW font de la recherche approximative (ANN) : ils explorent une fraction de l'espace pour répondre vite, et peuvent manquer certains voisins que le scan exact aurait trouvés. C'est le compromis rappel-latence. On le pilote avec ivfflat.probes ou hnsw.ef_search : augmenter ces valeurs rapproche les résultats de la recherche exacte, en échange de requêtes plus lentes.

Quel opérateur de distance utiliser pour un RAG avec Supabase ?

La distance cosinus est le choix standard pour des embeddings de modèles de langage : elle compare l'orientation des vecteurs indépendamment de leur norme. Dans pgvector, cela correspond à l'opérateur <=> et à une classe d'opérateurs cosinus (par exemple vector_cosine_ops) au moment de la création de l'index. L'essentiel est la cohérence : l'index doit être créé avec la même classe d'opérateurs que celle utilisée dans les requêtes.

Bundle FlowKit Complet

269 €