Optimisation de la connexion
Les connexions de base de données sont des ressources coûteuses. Chaque connexion consomme de la mémoire du serveur, nécessite une surcharge d’authentification et compte par rapport aux limites du serveur. Pour les applications IA qui effectuent des requêtes vectorielles fréquentes, une gestion efficace des connexions est essentielle pour atteindre un débit élevé sans épuiser les ressources.
Note
Les exemples de code de cette unité illustrent les modèles de gestion des connexions pour Python (psycopg) et .NET (Npgsql). Ces bibliothèques sont fréquemment mises à jour. Pour plus d’informations sur l’API actuelle, consultez la documentation psycopg et Npgsql .
Surcharge de connexion
La création d’une connexion PostgreSQL implique plusieurs étapes, chacune ajoutant une latence :
- Liaison TCP : Établissement de la connexion réseau (généralement 1 à 3 allers-retours)
- Négociation TLS : Chiffrement de la connexion (obligatoire pour Azure Database pour PostgreSQL)
- Authentification: Vérification des informations d’identification (mot de passe ou échange de jetons)
- Allocation de processus serveur : PostgreSQL génère un processus principal pour chaque connexion
- Initialisation de session : Définition des paramètres de session et du chargement des configurations
Cette séquence prend 50 à 200 millisecondes en fonction de la latence réseau et de la charge du serveur. Pour un moteur de recommandation qui gère des milliers de requêtes par seconde, la création de nouvelles connexions par requête consomme plus de temps dans la configuration de la connexion que dans l’exécution réelle de la requête.
Azure Database pour PostgreSQL limite les connexions simultanées en fonction du niveau de calcul. Les B1ms extensibles autorisent 50 connexions, les vCores à usage général 2 autorisent 859 connexions, les vCores à usage général 4 autorisent 1 718 connexions, les 4 vCores à mémoire optimisée autorisent 3 437 connexions et les 16 vCores à mémoire optimisée autorisent 5 000 connexions. Le dépassement de ces limites entraîne des échecs de connexion. Les applications qui créent des connexions par requête peuvent rapidement atteindre ces limites pendant les pics de trafic.
Regroupement de connexions avec PgBouncer
PgBouncer est un pool de connexions léger qui se trouve entre votre application et PostgreSQL. Il gère un pool de connexions de base de données et multiplexe les connexions clients parmi elles, réduisant considérablement le nombre de connexions de base de données réelles nécessaires.
Azure Database pour PostgreSQL inclut la prise en charge intégrée de PgBouncer sur les niveaux de calcul général et optimisé pour la mémoire. Le niveau Burstable ne prend pas en charge cette fonctionnalité. Activez PgBouncer via le portail Azure ou l’interface CLI. Une fois activé, connectez-vous via le port 6432 (port PgBouncer) au lieu de 5432 (port PostgreSQL direct). La chaîne de connexion PgBouncer utilise postgresql://user:password@myserver.postgres.database.azure.com:6432/mydb.
az postgres flexible-server parameter set \
--resource-group myResourceGroup \
--server-name myserver \
--name pgbouncer.enabled \
--value true
PgBouncer prend en charge trois modes de regroupement, chacun avec des compromis différents. Le mode session signifie qu’un client contient une connexion serveur pour l’ensemble de la session (jusqu’à ce qu’il se déconnecte). Ce mode prend en charge toutes les fonctionnalités PostgreSQL, mais offre une réduction minimale de la connexion. Le mode transactionnel signifie qu’un client conserve une connexion serveur uniquement pendant une transaction. Entre les transactions, la connexion retourne au pool. Ce mode fonctionne bien pour la plupart des applications et réduit considérablement les exigences de connexion. Le mode déclaration signifie qu’un client obtient une connexion uniquement pour des déclarations individuelles. Ce mode fournit une réduction maximale de la connexion, mais ne prend pas en charge les transactions multi-instructions. Pour les charges de travail de recherche vectorielle, le mode transactionnel est généralement le meilleur choix.
PgBouncer expose plusieurs paramètres qui contrôlent le comportement du pool, les limites de connexion et la gestion du délai d’expiration. Pour les charges de travail de recherche vectorielle avec des modèles de trafic en rafale, le réglage de ces paramètres permet d’équilibrer la disponibilité des connexions par rapport à la consommation de ressources. Configurez pgbouncer.default_pool_size (20 à 50 en fonction des besoins de concurrence), pgbouncer.max_client_conn (5 000 + pour les applications à trafic élevé), pgbouncer.pool_mode (transaction) et pgbouncer.query_wait_timeout (30 à 120 secondes).
Le mode transaction retourne des connexions au pool après chaque validation ou annulation de transaction. Cela affecte plusieurs fonctionnalités PostgreSQL. Les variables de session sont réinitialisées entre les transactions. Par conséquent, les paramètres appliqués SET ne sont pas conservés entre les transactions. Utilisez SET LOCAL dans les transactions ou configurez le côté serveur par défaut. Les instructions préparées peuvent ne pas fonctionner, car les instructions préparées nommées sont liées aux connexions. En mode transactionnel, une instruction préparée créée dans une transaction peut ne pas être disponible dans la prochaine si une autre connexion est affectée. LISTEN/NOTIFY ne fonctionne pas, car ces fonctionnalités nécessitent des connexions persistantes et sont incompatibles avec le regroupement de transactions. Pour les applications de recherche vectorielle, ces limitations sont rarement problématiques, car les requêtes sont généralement des sélections simples sans état spécifique à la session.
Regroupement de connexions au niveau de l’application
En plus (ou au lieu de) PgBouncer, votre application peut gérer directement les pools de connexions. Cela permet un contrôle plus précis sur le cycle de vie des connexions et s’intègre aux infrastructures d’application.
Le psycopg_pool package fournit un regroupement de connexions pour psycopg. Les pools au niveau de l’application vous permettent de contrôler le cycle de vie des connexions, le comportement du délai d'inactivité et le contrôle de l’état de santé. Ils s’intègrent également naturellement à la gestion et à la journalisation des erreurs de votre application. Lorsqu’ils sont combinés avec PgBouncer, les pools d’applications gèrent la gestion des connexions locales, tandis que PgBouncer gère le multiplexage côté serveur. Le with pool.connection() gestionnaire de contexte retourne automatiquement la connexion au pool lorsque le bloc se ferme, même si une exception se produit.
from psycopg_pool import ConnectionPool
# Create a connection pool
pool = ConnectionPool(
conninfo="postgresql://user:password@myserver.postgres.database.azure.com:6432/mydb",
min_size=5, # Minimum connections to maintain
max_size=20, # Maximum connections allowed
max_idle=300, # Close idle connections after 5 minutes
max_lifetime=3600 # Recycle connections after 1 hour
)
# Use connections from the pool
def search_similar_products(query_embedding, limit=10):
with pool.connection() as conn:
with conn.cursor() as cur:
cur.execute("""
SELECT id, name, embedding <=> %s AS distance
FROM products
ORDER BY embedding <=> %s
LIMIT %s
""", (query_embedding, query_embedding, limit))
return cur.fetchall()
Npgsql inclut le regroupement de connexions intégré activé par défaut. Vous n’avez donc pas besoin d’un package distinct. Le pool gère automatiquement la création, la réutilisation et la suppression des connexions en fonction des paramètres que vous spécifiez dans la chaîne de connexion. Chaque chaîne de connexion unique conserve son propre pool, de sorte que les chaînes de connexion cohérentes dans votre application garantissent une utilisation efficace du pool. Lorsque vous appelez conn.Close() ou que la connexion est supprimée, elle revient au pool au lieu d’être détruite. Configurez le regroupement par le biais de paramètres de chaîne de connexion tels que Minimum Pool Size=5;Maximum Pool Size=20;Connection Idle Lifetime=300;Connection Lifetime=3600.
La taille du pool affecte à la fois les performances et la consommation des ressources. Si un pool dimensionné trop petit entraîne l'attente des requêtes pour des connexions disponibles, cela augmente la latence. Définir la taille trop grande gaspille la mémoire et peut surcharger le serveur de base de données. La taille appropriée dépend de vos modèles de trafic, de la durée des requêtes et du nombre d’instances d’application partageant la base de données. Conservez la taille minimale suffisante pour gérer le trafic de référence sans attendre. Limitez la taille maximale à ce que la base de données peut gérer divisée par le nombre d’instances d’application. Si vous avez 10 instances d'application et que votre base de données prend en charge 1 000 connexions, définissez un maximum de 100 par instance (en laissant de la marge). Recyclez régulièrement les connexions (toutes les 30 à 60 minutes) pour maintenir l’intégrité, car les connexions de longue durée peuvent accumuler des fuites de mémoire ou contenir des plans mis en cache obsolètes.
Gestion des sessions pour les charges de travail d'intelligence artificielle
Certaines requêtes vectorielles tirent parti des paramètres au niveau de la session qui allouent plus de ressources à la requête que les valeurs par défaut au niveau du serveur autorisent.
Les requêtes de similarité vectorielle qui trient de grands ensembles de résultats bénéficient d'un accroissement de work_mem. Définissez-le pour des sessions ou des transactions spécifiques à l’aide de SET LOCAL work_mem = '256MB'.
SET LOCAL s’applique uniquement dans la transaction actuelle. Lorsque la transaction se termine, le paramètre revient à la valeur par défaut, ce qui est sécurisé pour les connexions mises en pool.
Ajustez hnsw.ef_search ou ivfflat.probes pour les requêtes avec des exigences de précision différentes. Utilisez SET LOCAL hnsw.ef_search = 200 pour un rappel plus élevé dans les requêtes où la précision est critique ou SET LOCAL hnsw.ef_search = 20 pour des requêtes plus rapides où les résultats approximatifs sont acceptables. Ce modèle vous permet d’équilibrer la précision et la vitesse en fonction du cas d’usage spécifique sans affecter d’autres requêtes.
Modèles d’utilisation efficaces du Kit de développement logiciel (SDK)
Au-delà de la gestion des connexions, la façon dont vous structurez les interactions de base de données affecte les performances.
Les allers-retours réseau ajoutent une latence à chaque opération de base de données. Lorsque vous avez besoin de plusieurs éléments de données, les extraire dans une seule requête élimine la surcharge par requête de transmission du réseau, l’analyse des requêtes et la sérialisation des résultats. Pour les applications IA qui récupèrent des embeddings pour plusieurs éléments, le traitement par lots peut réduire la latence totale de centaines de millisecondes à des valeurs à un chiffre. Au lieu d’effectuer plusieurs allers-retours avec des requêtes individuelles, utilisez une requête unique avec WHERE id = ANY(%s) et transmettez une liste d’ID.
Pour charger un grand nombre de vecteurs, la commande PostgreSQL COPY est considérablement plus rapide que les instructions individuelles INSERT .
COPY diffuse des données directement dans la table dans un format binaire ou texte, en contournant la surcharge d’analyse des instructions SQL individuelles. Lorsque vous chargez des données incorporées à partir de pipelines de traitement par lots ou de migrations de données initiales, COPY vous pouvez réduire les temps de chargement des heures à minutes.
COPY peut charger des centaines de milliers de lignes par seconde, tandis que les insertions individuelles sont limitées à des milliers par seconde.
Lorsque votre application peut paralléliser le travail, les opérations de base de données asynchrones améliorent le débit en exécutant plusieurs requêtes simultanément sans bloquer les threads. Ce modèle est utile pour les charges de travail IA qui doivent rechercher simultanément plusieurs collections vectorielles ou combiner la recherche vectorielle avec d’autres récupérations de données. Les pools asynchrones gèrent efficacement les connexions entre les opérations simultanées tout en respectant les limites de taille de pool. Utilisez AsyncConnectionPool de psycopg_pool et asyncio.gather pour exécuter plusieurs recherches simultanément.
Résilience des connexions
Les problèmes réseau, les redémarrages du serveur et les basculements peuvent interrompre les connexions de base de données. Les applications robustes gèrent ces situations avec aisance.
Les défaillances temporaires telles que les blips réseau, les réinitialisations de connexion et l’indisponibilité brève du serveur pendant la maintenance sont inévitables dans les environnements cloud. L’implémentation d’une logique de nouvelle tentative avec interruption exponentielle permet à votre application de récupérer correctement à partir de ces problèmes temporaires sans surcharger le serveur avec des tentatives de nouvelle tentative immédiate. Ajoutez une gigue aléatoire pour empêcher plusieurs instances d’application de réessayer simultanément. Interceptez les exceptions, calculez le temps d’attente en tant que (2 ** attempt) + random.uniform(0, 1), puis réessayez jusqu’à un nombre maximal de tentatives.
Les délais d’expiration empêchent votre application d’attendre indéfiniment lorsque la base de données est lente ou inaccessible. Les délais d’expiration des connexions limitent la durée d’attente pendant l’établissement de nouvelles connexions, tandis que les délais d’attente des instructions limitent le temps d’exécution de la requête. Pour les applications de recherche vectorielle, choisissez des délais d’expiration qui prennent en charge vos requêtes légitimes les plus lentes tout en échouant rapidement sur les requêtes qui dépassent la latence acceptable. Configurez les délais d’expiration dans votre chaîne de connexion à l’aide de paramètres tels que connect_timeout=10 et options=-c statement_timeout=30000. Pour les requêtes vectorielles, définissez les délais d’expiration des instructions qui prennent en charge vos requêtes les plus lentes acceptables. Un délai d’expiration de 30 secondes est raisonnable pour les recherches vectorielles complexes ; Les applications interactives peuvent utiliser des valeurs inférieures.
Lorsque toutes les connexions de pool sont en cours d’utilisation et que de nouvelles requêtes arrivent, le pool doit soit mettre en file d’attente les demandes (ajout de latence) soit les rejeter immédiatement. Aucune des options n’est idéale, ainsi la surveillance de l’utilisation du pool vous permet d’adapter les ressources avant que l’épuisement ne se produise fréquemment. Lorsque l’épuisement se produit, le renvoi d’un message d’erreur clair permet aux clients d’implémenter leur propre logique de nouvelle tentative plutôt que d’expirer inutilement. Gérez les PoolTimeout exceptions en retournant une erreur normale comme {"error": "Service temporarily busy, please retry"}. Surveillez l’utilisation du pool et effectuez une mise à l’échelle si un épuisement fréquent se produit.