Optimiser PostgreSQL pour pgvector
Les charges de travail de recherche vectorielle mettent en place différentes demandes sur PostgreSQL par rapport aux requêtes transactionnelles ou analytiques traditionnelles. La compréhension de ces différences vous permet de paramétrer les paramètres de configuration pour optimiser la latence des requêtes, l’utilisation de la mémoire et l’efficacité du calcul pour les applications IA.
Note
Les exemples de code de cette unité illustrent les modèles de configuration pour PostgreSQL et pgvector. Les valeurs des paramètres affichées sont des points de départ pour le réglage. Les paramètres optimaux dépendent de votre charge de travail, de votre taille de jeu de données et du matériel spécifiques. Évaluez toujours les modifications dans un environnement de test avant de les appliquer à la production.
Exigences relatives au calcul et à la mémoire pgvector
La recherche de similarité vectorielle implique l’informatique des distances entre un vecteur de requête et des millions de vecteurs stockés potentiellement. Ce modèle de calcul diffère fondamentalement des opérations de base de données traditionnelles qui filtrent les lignes en fonction de colonnes indexées ou de tables de jointure sur les valeurs de clé.
Lorsque vous exécutez une requête de similarité vectorielle, pgvector doit calculer la distance entre votre vecteur de requête et les vecteurs candidats. Pour une incorporation à 1536 dimensions (commune aux modèles OpenAI), chaque calcul de distance implique 1 536 opérations à virgule flottante. La recherche d’un million de vecteurs sans index nécessite plus de 1,5 milliard d’opérations à virgule flottante par requête. Les trois fonctions de distance ont des coûts de calcul différents qui affectent votre choix en fonction des caractéristiques de vos données et des exigences de performances.
-
Distance L2 (Euclidean) : Utilise l’opérateur
<->et calcule la racine carrée de la somme des différences carrées. Il s’agit de l’option la plus coûteuse en matière de calcul. -
Distance cosinus : Utilise l’opérateur et mesure l’angle
<=>entre les vecteurs. Il normalise les vecteurs en interne, en ajoutant un calcul, mais en fournissant une similarité invariante à l’échelle. -
Produit interne : utilise l’opérateur
<#>et calcule le produit par points. Il s’agit de l’opération la plus rapide, mais nécessite des vecteurs prédéfinisés pour des comparaisons de similarité significatives.
Pour les moteurs de recommandation et la recherche sémantique, la distance de cosinus est souvent préférée, car elle gère les vecteurs de magnitudes variables de manière cohérente. Si vos incorporations sont déjà normalisées (de nombreuses API d’incorporation retournent des vecteurs normalisés), le produit interne fournit des résultats équivalents avec moins de calcul.
Les colonnes vectorielles consomment un stockage substantiel par rapport aux types de données traditionnels. Un vecteur unidimensionnel de 1536 stocké en tant que float4 (précision unique) nécessite 6 144 octets, plus une surcharge. Une table avec un million d’incorporations de produits a besoin d’environ 6 Go uniquement pour la colonne vectorielle. Quand PostgreSQL traite les requêtes vectorielles, il charge les données vectorielles en mémoire. La relation entre la mémoire disponible et la taille des données vectorielles affecte directement si les requêtes peuvent s’exécuter efficacement en mémoire ou doivent lire à plusieurs reprises à partir du disque.
Les incorporations à dimensions supérieures offrent une résolution sémantique plus importante, mais augmentent les coûts de stockage et de calcul quadratiquement. Un vecteur 3072 dimensionnel (utilisé par certains modèles d’incorporation plus récents) nécessite quatre fois le travail de calcul de distance et deux fois le stockage d’un vecteur 1536 dimensionnel. Tenez compte de vos exigences de précision lors du choix des dimensions d’incorporation. Pour de nombreuses applications de recommandation et de recherche, 768 ou 1 024 dimensions offrent une qualité suffisante avec une consommation de ressources significativement inférieure.
Configuration de la mémoire pour les charges de travail vectorielles
Les paramètres de mémoire de PostgreSQL affectent considérablement les performances des requêtes vectorielles. Le réglage approprié garantit que les index vectoriels et les données fréquemment sollicitées restent en mémoire, ce qui réduit les opérations de disque coûteuses.
Le paramètre contrôle le shared_buffers cache de mémoire partagée de PostgreSQL, où résident fréquemment les pages de données. Pour les charges de travail vectorielles, ce cache doit être suffisamment grand pour contenir vos index vectoriels et vos données chaudes. Un taux d’accès au cache inférieur à 99 % pour les charges de travail gourmandes en vecteurs indique que shared_buffers est peut-être trop petit. Sur Azure Database pour PostgreSQL, ce paramètre est paramétré automatiquement en fonction de votre niveau de calcul, mais vous pouvez l’ajuster dans la plage autorisée pour votre niveau. Pour les charges de travail de recherche vectorielle dédiées, visez shared_buffers suffisamment grand pour contenir vos index vectoriels, ainsi qu’une marge pour d’autres données mises en cache. Un point de départ est de 25% de mémoire disponible, avec des augmentations basées sur la surveillance. Les requêtes suivantes vous aident à vérifier vos paramètres actuels et les performances du cache.
-- Check current setting
SHOW shared_buffers;
-- View buffer cache hit ratio
SELECT
sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) AS cache_hit_ratio
FROM pg_statio_user_tables;
Le paramètre work_mem contrôle la mémoire disponible pour chaque opération de requête individuelle, telles que les tris et les jointures de hachage. Requêtes de similarité vectorielle, en particulier celles qui combinent la recherche vectorielle avec le filtrage et le tri, bénéficient d'une work_mem adéquate. La valeur par défaut work_mem (généralement 4 Mo) est souvent trop petite pour les opérations vectorielles qui doivent trier les résultats par similarité. Vous pouvez augmenter cette valeur pour les sessions ou les requêtes qui effectuent des recherches vectorielles avec des jeux de résultats volumineux à l'aide de SET work_mem = '256MB';. Soyez prudent avec les augmentations globales de work_mem car ce paramètre s’applique à chaque opération par connexion. Ainsi, un serveur qui gère 100 connexions simultanées avec des requêtes complexes peut consommer 100 × work_mem × opérations par requête en mémoire. Pour les charges de travail vectorielles, envisagez de définir work_mem au niveau de la session pour des requêtes spécifiques plutôt que globalement.
Le effective_cache_size paramètre indique au planificateur de requêtes la quantité de mémoire disponible pour la mise en cache, y compris le cache de fichiers de shared_buffers PostgreSQL et le cache de fichiers du système d’exploitation. Ce paramètre n’alloue pas de mémoire, mais influence si le planificateur choisit les analyses d’index sur les analyses séquentielles. Définir effective_cache_size sur environ 75% de la mémoire système totale sur les serveurs de base de données dédiés. Les valeurs plus élevées encouragent le planificateur à utiliser des index, ce qui est généralement bénéfique pour la recherche vectorielle. Sur Azure Database pour PostgreSQL, celui-ci est configuré automatiquement en fonction de votre niveau.
Paramètres du planificateur de requêtes pour la recherche vectorielle
Le planificateur de requêtes de PostgreSQL prend des décisions sur la façon d’exécuter des requêtes en fonction des estimations de coûts. Plusieurs paramètres affectent ces estimations et les réglages pour le stockage SSD moderne améliorent la planification des requêtes vectorielles.
Le random_page_cost paramètre estime le coût de lecture d’une page de disque aléatoire par rapport à une page séquentielle. La valeur par défaut de la version 4.0 reflète les caractéristiques du disque épinglant où l’accès aléatoire est beaucoup plus lent que l’accès séquentiel. Azure Database pour PostgreSQL utilise le stockage SSD où l’accès aléatoire et séquentiel a des performances similaires. L’abaissement de random_page_cost à 1.1-1.5 encourage le planificateur à utiliser plus fréquemment les scans d’index, ce qui améliore l'efficacité des recherches vectorielles accédant à des pages de données dispersées. Vous pouvez ajuster ce paramètre avec SET random_page_cost = 1.1;.
Le effective_io_concurrency paramètre indique à PostgreSQL le nombre d’opérations d’E/S de disque simultanées que le système de stockage peut gérer. Les valeurs supérieures permettent aux analyses de tas bitmap de prérécupérer davantage de pages en parallèle. Le stockage SSD gère bien les E/S simultanées, donc défini effective_io_concurrency sur 200 pour les instances Azure Database pour PostgreSQL basées sur SSD. Cela améliore les performances des requêtes qui combinent la similarité vectorielle avec le filtrage des métadonnées.
Les paramètres parallel_tuple_cost et parallel_setup_cost contrôlent quand PostgreSQL utilise l'exécution de requêtes parallèles. Les opérations vectorielles peuvent tirer parti du parallélisme, en particulier pour les analyses séquentielles sur des tables volumineuses. Les valeurs inférieures pour parallel_tuple_cost (par défaut 0.1) et parallel_setup_cost (par défaut 1000) encouragent l’exécution parallèle. Pour les charges de travail vectorielles avec des tables volumineuses, l’activation du parallélisme peut réduire considérablement le temps de requête lorsque les index ne sont pas utilisés. Vous pouvez vérifier vos paramètres parallèles actuels à l’aide de SHOW parallel_tuple_cost;, SHOW parallel_setup_cost; et SHOW max_parallel_workers_per_gather;.
Configurer des paramètres spécifiques à pgvector
L’extension pgvector fournit des paramètres de configuration qui contrôlent le compromis de précision-vitesse pour les recherches basées sur des index. Ces paramètres sont essentiels pour le réglage des performances des requêtes vectorielles.
Lorsque vous utilisez des index IVFFlat, le paramètre ivfflat.probes contrôle le nombre de partitions d’index recherchées pour chaque requête. Les valeurs plus élevées augmentent le rappel (trouver plus de vrais voisins les plus proches) mais ralentissent les requêtes. Ce compromis est central pour le réglage des performances IVFFlat. Vous équilibrez le risque d’absence de bonnes correspondances par rapport au coût de la recherche de partitions supplémentaires. La valeur par défaut de 1 recherche uniquement la seule partition la plus prometteuse, qui est rapide, mais peut manquer les résultats pertinents stockés dans des partitions adjacentes. Pour les moteurs de recommandation, le fait de manquer une bonne correspondance dégrade l’expérience utilisateur. Commencez par définir ivfflat.probes à 5 à 10% de votre paramètre lists et ajustez en fonction du rappel mesuré.
-- Configure IVFFlat search depth
SET ivfflat.probes = 10;
-- Execute vector search
SELECT id, name, embedding <=> $1 AS distance
FROM products
ORDER BY embedding <=> $1
LIMIT 10;
Pour les index HNSW, le hnsw.ef_search paramètre contrôle la taille de la liste de candidats dynamiques pendant la recherche. Les valeurs plus grandes explorent davantage le graphe, ce qui améliore le rappel au détriment de la rapidité. Contrairement aux partitions discrètes d’IVFFlat, la structure de graphiques de HNSW signifie que ce paramètre affecte la façon dont l’algorithme explore soigneusement les connexions voisines avant de retourner les résultats. La valeur par défaut de 40 fournit un équilibre raisonnable pour de nombreuses charges de travail. Pour les exigences à haute précision (telles que la recherche des correspondances top-10), augmentez à 100-200. Pour les applications critiques à latence où les résultats approximatifs sont acceptables, les valeurs aussi faibles que 20 peuvent fonctionner. Configurez hnsw.ef_search avec SET hnsw.ef_search = 100; avant d’exécuter votre recherche vectorielle. La valeur optimale dépend de vos exigences de précision et de votre budget de latence. Évaluez les requêtes représentatives pour trouver l'équilibre optimal pour votre application.
Surveiller et mesurer les performances
Le réglage sans mesure est approximatif. Utilisez les outils intégrés de PostgreSQL et Azure Monitor pour comprendre le comportement des requêtes et valider les modifications de configuration.
La EXPLAIN ANALYZE commande montre comment PostgreSQL exécute une requête et fournit des informations de minutage réelles. Pour les requêtes vectorielles, cela indique si les index sont utilisés et où le temps est passé. Comprendre le plan d’exécution vous permet d’identifier si des performances médiocres proviennent d’index manquants, de paramètres non optimaux ou de problèmes de distribution de données. Exécutez EXPLAIN ANALYZE avant votre requête vectorielle pour voir le plan d’exécution. Recherchez l’analyse d’index à l’aide de [index_name] (indique que l’index vectoriel est utilisé), l’analyse Seq (indique une analyse séquentielle, qui est lente pour les tables volumineuses), les valeurs de temps réel (indiquent où le temps d’exécution est passé) et les nombres de lignes (aide à identifier si le filtrage fonctionne efficacement). Si vous voyez des analyses séquentielles alors que vous vous attendez à une utilisation de l'index, vérifiez que l'opérateur de distance de la requête correspond à la classe d'opérateur de l'index (par exemple, en utilisant <=> avec un index créé à l'aide de vector_cosine_ops).
Parfois PostgreSQL choisit de ne pas utiliser d’index disponible. Les raisons courantes des requêtes vectorielles incluent des requêtes qui retournent une grande partie de la table (la surcharge d’index dépasse l’analyse séquentielle), les statistiques obsolètes après des modifications significatives des données ou un opérateur de distance qui ne correspond pas à la classe d’opérateur de l’index. Exécutez ANALYZE products; pour mettre à jour les statistiques pour une planification précise. Vous pouvez vérifier les informations d’index avec SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'products';.
Azure Database pour PostgreSQL expose des métriques via Azure Monitor qui permettent d’identifier les goulots d’étranglement des performances. Surveillez le pourcentage de CPU (un pourcentage élevé d'utilisation de CPU soutenue indique des opérations vectorielles liées au calcul), le pourcentage de mémoire (l'approche des limites suggère d'augmenter le niveau de calcul ou d’optimiser les requêtes), le pourcentage d’E/S de stockage (les valeurs élevées indiquent que les données ne tiennent pas dans le cache) et les connexions actives (s’approcher des limites indique qu'un regroupement des connexions pourrait être utile). Configurez des alertes pour ces métriques afin d’intercepter la dégradation des performances avant d’affecter les utilisateurs.
Meilleures pratiques pour le réglage pgvector
Le réglage efficace suit une approche systématique plutôt que des modifications de paramètres aléatoires.
- Établissez d’abord des lignes de base : Mesurez la latence des requêtes et l’utilisation des ressources avant d’apporter des modifications. Sans lignes de base, vous ne pouvez pas déterminer si les modifications aident ou font mal.
- Modifiez un paramètre à la fois : Plusieurs modifications simultanées rendent impossible les améliorations d’attributs ou les régressions apportées à des paramètres spécifiques.
- Testez avec des données de type production : Les performances des requêtes varient considérablement avec la taille et la distribution des données. L'ajustement sur de petits jeux de données de test produit souvent des paramètres qui échouent à grande échelle.
- Surveillez les régressions : Les paramètres qui améliorent la recherche vectorielle peuvent affecter négativement d’autres charges de travail. Surveillez l’intégrité globale du système après les modifications.
- Documentez vos paramètres : Enregistrez ce que vous avez changé, pourquoi et quel effet il avait. Cette documentation est précieuse lors de la résolution des problèmes futurs.