dimanche 9 mars 2025

Introduction HikariCP pour les développeurs

La gestion de pool de connexions en bases de données

La gestion de pool de connexions est une technique essentielle pour optimiser les performances des applications qui utilisent des bases de données. Voici une explication de son utilité et des coûts associés à la création de connexions:

Pourquoi utiliser un pool de connexions?

Les connexions aux bases de données sont coûteuses à établir pour plusieurs raisons:

  1. Temps d'établissement - Chaque nouvelle connexion nécessite:

    • Une authentification (vérification des identifiants)
    • Une négociation du protocole
    • Une allocation de ressources côté serveur
  2. Ressources consommées:

    • Mémoire: Chaque connexion maintient des buffers et des structures de données en mémoire
    • Descripteurs de fichiers (sockets) : Ressource limitée au niveau du système d'exploitation
    • Threads/processus côté serveur: La base de données doit dédier des ressources de calcul

Comment fonctionne un pool de connexions?

Un pool maintient un ensemble de connexions ouvertes qui sont réutilisées par l'application:

  • Les connexions sont créées à l'avance ou au fur et à mesure des besoins
  • Quand une opération de base de données est nécessaire, l'application emprunte une connexion du pool
  • Une fois l'opération terminée, la connexion est rendue au pool plutôt que d'être fermée
  • Si toutes les connexions sont utilisées, l'application peut attendre qu'une se libère ou créer une nouvelle connexion (selon la configuration)

Bénéfices en termes de performance

  • Réduction du temps de latence : Élimination du temps d'établissement des connexions
  • Économie des ressources: Réutilisation plutôt que création/destruction constante
  • Meilleure scalabilité : Gestion du nombre maximal de connexions simultanées
  • Distribution de charge : Partage efficace des ressources disponibles

Coûts de création d'une connexion

En termes concrets, voici ce que coûte la création d'une nouvelle connexion:

  • CPU : Traitement cryptographique pour l'authentification, initialisation des structures
  • Mémoire : 2-10 Mo par connexion selon le SGBD et sa configuration
  • E/S réseau : Plusieurs allers-retours réseau pour établir la connexion
  • Temps : Typiquement entre 10-100ms selon la charge du serveur et le réseau

Sans pool de connexions, une application web traitant 1000 requêtes simultanées pourrait tenter de créer 1000 connexions distinctes, consommant potentiellement plusieurs Go de RAM et surchargeant le serveur de base de données.

Un pool bien configuré pourrait gérer ces mêmes 1000 requêtes avec seulement 10-20 connexions réutilisées, réduisant considérablement la consommation de ressources.

Librairies Maven pour la gestion de pool de connexions en Java

Voici les principales librairies de gestion de pool de connexions disponibles via Maven pour les applications Java :

HikariCP

Une des plus populaires et performantes actuellement :

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.0.1</version>
</dependency>

HikariCP est réputée pour sa légèreté et ses performances exceptionnelles.

Apache DBCP2

Solution mature de la fondation Apache :

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.9.0</version>
</dependency>

Tomcat JDBC Connection Pool

Développée pour Tomcat mais utilisable indépendamment :

<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jdbc</artifactId>
    <version>10.1.12</version>
</dependency>

C3P0

Une solution éprouvée bien que plus ancienne :

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.5</version>
</dependency>

Vibur DBCP

Pool de connexions à haute performance :

<dependency>
    <groupId>org.vibur</groupId>
    <artifactId>vibur-dbcp</artifactId>
    <version>25.0</version>
</dependency>

Spring Boot DataSource

Si vous utilisez Spring Boot, le pool de connexions est intégré :

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>3.1.2</version>
</dependency>

Spring Boot utilise HikariCP par défaut depuis la version 2.0.

Note importante

Dans les projets modernes, HikariCP est généralement le premier choix en raison de ses performances supérieures et de sa simplicité de configuration. C'est d'ailleurs la raison pour laquelle Spring Boot l'a adopté comme solution par défaut.

HikariCP - Un pool de connexions haute performance pour Java

HikariCP est une bibliothèque Java qui implémente un pool de connexions JDBC extrêmement rapide et léger. Créée par Brett Wooldridge, elle est devenue la référence en matière de gestion de connexions aux bases de données dans l'écosystème Java.

Caractéristiques principales

  • Performance exceptionnelle : Souvent considérée comme la solution de pool de connexions la plus rapide disponible pour Java
  • Faible empreinte mémoire : Conçue pour être légère et utiliser un minimum de ressources
  • Simplicité : Configuration minimale requise pour démarrer
  • Fiabilité : Gestion robuste des erreurs et récupération automatique
  • Métriques intégrées : Surveillance des performances via JMX

Pourquoi HikariCP est si populaire

  1. Optimisations poussées :
    • Utilisation optimisée des structures de données Java
    • Réduction des contentions grâce à des algorithmes spécifiques
    • Élimination des opérations inutiles présentes dans d'autres pools
  2. Adoption généralisée :
    • Intégré par défaut dans Spring Boot depuis la version 2.0
    • Utilisé par de nombreux frameworks et projets d'entreprise
  3. Configurable mais simple :
    • Fonctionne bien avec les paramètres par défaut
    • Paramètres clairs pour les ajustements avancés

Le nom "Hikari" vient du japonais et signifie "lumière" ou "éclat", ce qui reflète bien la philosophie de cette bibliothèque : être rapide et légère.

Paramètres de configuration de HikariCP avec leurs valeurs par défaut

Voici les principaux paramètres de configuration de HikariCP avec leurs valeurs par défaut :

Paramètres essentiels

  • dataSourceClassName ou jdbcUrl : null (l'un des deux est requis)
  • username : null (requis)
  • password : null (requis)

Paramètres de dimensionnement du pool

  • maximumPoolSize : 10 (nombre maximum de connexions dans le pool)
  • minimumIdle : égal à maximumPoolSize (nombre minimum de connexions inactives)
  • connectionTimeout : 30000 ms (30 secondes, temps d'attente maximum pour une connexion)
  • idleTimeout : 600000 ms (10 minutes, temps maximum d'inactivité d'une connexion)
  • maxLifetime : 1800000 ms (30 minutes, durée de vie maximale d'une connexion)
  • keepaliveTime : 0 ms (désactivé par défaut, intervalle pour tester les connexions inactives)

Paramètres de validation et test

  • connectionTestQuery : null (requête SQL pour valider les connexions, auto-détectée par défaut)
  • validationTimeout : 5000 ms (5 secondes, temps maximum pour tester une connexion)
  • initializationFailTimeout : 1 (temps en ms pour réessayer l'initialisation du pool)
  • isolateInternalQueries : false (isolation des requêtes internes)

Paramètres de performances

  • autoCommit : true (comportement auto-commit des connexions)
  • catalog : driver default (catalogue par défaut pour les connexions)
  • schema : driver default (schéma par défaut pour les connexions)
  • leakDetectionThreshold : 0 (désactivé, temps en ms pour détecter les fuites de connexions)

Paramètres de nom et logging

  • poolName : auto-généré ("HikariPool-#") (nom du pool pour le logging et JMX)
  • registerMbeans : false (enregistrement des MBeans pour monitoring)
  • allowPoolSuspension : false (possibilité de suspendre le pool)

Paramètres de transactions

  • transactionIsolation : null (niveau d'isolation des transactions)
  • readOnly : false (connexions en lecture seule)

Ces paramètres peuvent être configurés soit par programmation via HikariConfig, soit via un fichier de propriétés ou en utilisant les propriétés de Spring Boot si vous utilisez ce framework.

Pour les applications à forte charge, il est souvent recommandé d'ajuster au moins les paramètres maximumPoolSize, connectionTimeout et maxLifetime en fonction des caractéristiques spécifiques de votre application et de votre base de données.


Connexions actives vs connexions inactives dans HikariCP

Connexion active

Une connexion est considérée comme active lorsque :

  • Elle a été empruntée du pool via dataSource.getConnection()
  • Elle est actuellement utilisée par le code applicatif pour exécuter des opérations de base de données
  • Elle n'a pas encore été retournée au pool (pas de connection.close() appelé)

En termes concrets, une connexion active est une connexion qui est "entre les mains" du code applicatif et qui n'est donc pas disponible pour d'autres parties de l'application.

Connexion inactive

Une connexion est considérée comme inactive lorsque :

  • Elle est établie avec la base de données et fonctionnelle
  • Elle est présente dans le pool et prête à être utilisée
  • Elle n'est pas actuellement utilisée par le code applicatif (elle est au repos dans le pool)
  • Elle a été retournée au pool après utilisation via connection.close()

Les connexions inactives sont celles qui sont disponibles immédiatement pour être fournies quand un appel à getConnection() est effectué.

Dans le contexte des paramètres de HikariCP

  • minimumIdle : Nombre minimum de connexions inactives à maintenir dans le pool
  • maximumPoolSize : Nombre total maximum de connexions (actives + inactives)
  • idleTimeout : S'applique uniquement aux connexions inactives qui excèdent minimumIdle
  • maxLifetime : S'applique à toutes les connexions, qu'elles soient actives ou inactives

Exemple pratique

Dans un pool avec maximumPoolSize=20 et minimumIdle=5 :

  • Si 8 connexions sont empruntées (actives), 12 sont inactives dans le pool
  • Si l'application reste peu chargée pendant un temps dépassant idleTimeout, HikariCP peut réduire les connexions inactives jusqu'à 5 (minimumIdle)
  • Lors d'un pic d'activité, si 18 connexions sont empruntées (actives), il reste 2 connexions inactives
  • Si une connexion reste empruntée (active) au-delà de maxLifetime, elle sera marquée pour fermeture dès qu'elle sera retournée au pool

Le suivi de l'état actif/inactif des connexions est une part essentielle de la stratégie de HikariCP pour optimiser les performances et l'utilisation des ressources.

Le cycle de vie des connexions avec HikariCP

Voici une explication détaillée du processus de création et d'utilisation des connexions, du démarrage de l'application jusqu'à l'exécution des requêtes SQL :

1. Initialisation au démarrage de l'application

  • Création du HikariDataSource :

    • L'application instancie un objet HikariConfig avec les paramètres souhaités
    • Un HikariDataSource est créé avec cette configuration
    • Le pool commence son initialisation
  • Remplissage initial du pool :

    • Si minimumIdle > 0, HikariCP commence à établir des connexions
    • Des connexions sont créées jusqu'à atteindre minimumIdle (par défaut égal à maximumPoolSize)
    • Chaque connexion établie passe par le processus complet d'authentification à la base de données

2. Gestion des connexions au repos

  • État d'attente :
    • Les connexions établies restent actives dans le pool
    • Des vérifications périodiques (keepaliveTime si activé) maintiennent les connexions valides
    • Les connexions qui dépassent maxLifetime sont fermées et remplacées, même si elles sont inactives

3. Demande d'une connexion par le code applicatif

  • Appel à getConnection() :

    • L'application demande une connexion via dataSource.getConnection() : dataSource est du type : HikariDataSource
    • HikariCP vérifie d'abord s'il existe une connexion disponible dans le pool
  • Scénarios possibles :

    • Connexion disponible : Une connexion inactive est récupérée du pool et retournée immédiatement
    • Pool plein et actif : Si toutes les connexions sont utilisées mais maximumPoolSize n'est pas atteint, une nouvelle connexion est créée
    • Pool saturé : Si toutes les connexions sont utilisées et maximumPoolSize est atteint, l'application attend qu'une connexion se libère (jusqu'à connectionTimeout) : Si l'exécution de requête dépasse connectionTimeout ,  cela pourra bien engendre un problème de pool de connexion. Plus une requête s'exécute rapidement, plus la connexion est libérée et retournée dans le pool de connexion pour être reetulisée par les requêtes en attente

4. Validation de la connexion

  • Vérification avant utilisation :
    • HikariCP peut tester la validité de la connexion avant de la fournir
    • Si connectionTestQuery est spécifié, cette requête est exécutée pour vérifier que la connexion fonctionne
    • Sans connectionTestQuery, HikariCP utilise la méthode isValid() du JDBC

5. Exécution de la requête SQL

  • Utilisation de la connexion :
    • L'application utilise la connexion pour créer des Statement, PreparedStatement, etc.
    • Les requêtes SQL sont exécutées via ces objets
    • La connexion reste "empruntée" au pool pendant toute l'utilisation

6. Retour de la connexion au pool

  • Libération de la connexion :

    • L'application appelle connection.close()
    • Avec HikariCP, cet appel ne ferme pas réellement la connexion mais la retourne au pool
    • La connexion est marquée comme disponible pour d'autres requêtes
  • Maintenance du pool :

    • Si la connexion a dépassé maxLifetime, elle sera fermée définitivement puis remplacée
    • Si le nombre de connexions inactives dépasse minimumIdle, les connexions excédentaires peuvent être fermées après idleTimeout

Ce cycle se répète pour chaque requête SQL, avec l'avantage que les connexions sont réutilisées plutôt que recréées à chaque fois, ce qui permet d'économiser considérablement les ressources et d'améliorer les performances. Un bon résumé du processus que j'ai lu sur un article publié sur medium :


  1. When a connection is requested to be acquired, the connection pool looks for free connections.
  2. If the pool finds a free connection, it will handle it to the client (no time wasted to open tcp sockets and connection).
  3. If the pool doesn’t have a free connection, it will try to grow to its maximum allowed size (which is configurable).
  4. If the pool has already reached its maximum size, it will retry several times to acquire a connection before throwing an exception if it doesn’t find one.
  5. When the client closes the connection, it is released and returned to the pool without closing the underlying physical connection (big performance gain).

The connection pool does not return the physical connection to the client; instead, it offers a proxy or a handle. When a connection is in use, the pool changes its state to allocated to prevent two concurrent threads from using the same database connection. When the client calls the method to close the connection, the proxy notifies the pool to change the connection state to unallocated so it can be reused again.

Minimum-idle dans HikariCP : utilité et garantie

À quoi sert minimum-idle ?

Le paramètre minimum-idle définit le nombre minimum de connexions inactives que HikariCP doit maintenir dans le pool lorsque l'application est peu sollicitée. Il a plusieurs utilités importantes :

  1. Réduction du temps de latence pour les pics soudains :

    • Garantit qu'un certain nombre de connexions sont toujours prêtes à l'emploi
    • Évite d'avoir à créer de nouvelles connexions (coûteuses) lors d'une augmentation subite du trafic
  2. Équilibre entre ressources et réactivité :

    • Permet d'économiser des ressources en période de faible activité (moins que maximumPoolSize)
    • Maintient un niveau de préparation acceptable pour répondre rapidement aux requêtes
  3. Stabilisation des performances :

    • Réduit les variations de temps de réponse entre périodes calmes et actives
    • Particulièrement utile pour les applications avec un trafic irrégulier

Comment ce nombre est garanti

HikariCP utilise un mécanisme actif pour garantir que le nombre de connexions inactives ne descend pas sous le seuil de minimum-idle :

  1. Thread de maintenance dédié :

    • Un thread séparé surveille en permanence l'état du pool
    • Ce thread s'exécute périodiquement pour vérifier le nombre de connexions inactives
  2. Processus d'ajout de connexions :

    • Si le nombre de connexions inactives tombe en dessous de minimum-idle :
      • Le thread de maintenance crée de nouvelles connexions
      • Ces connexions sont ajoutées au pool à l'état inactif
      • Le processus continue jusqu'à ce que le seuil minimum-idle soit atteint
  3. Mécanisme de remplacement :

    • Si une connexion inactive est invalidée (par exemple, dépassement de maxLifetime)
    • Le thread de maintenance crée automatiquement une connexion de remplacement
  4. Comportement après échec de connexion :

    • Si une tentative de création de connexion échoue, HikariCP réessaie automatiquement
    • Un backoff exponentiel est appliqué pour éviter de surcharger la base de données

Si minimum-idle est défini à une valeur inférieure à maximumPoolSize (la valeur par défaut est égale à maximumPoolSize), HikariCP peut réduire le nombre de connexions pendant les périodes d'inactivité, pour ensuite augmenter ce nombre quand l'activité reprend, tout en garantissant qu'il y aura toujours au moins minimum-idle connexions disponibles immédiatement.

Différence entre idleTimeout et maxLifetime dans HikariCP

Ces deux paramètres contrôlent le cycle de vie des connexions dans le pool, mais à des fins différentes :

idleTimeout

  • Définition : Durée maximale (en millisecondes) pendant laquelle une connexion peut rester inactive dans le pool avant d'être fermée.
  • Valeur par défaut : 600 000 ms (10 minutes)
  • Objectif : Libérer les ressources inutilisées en période de faible charge
  • Comportement : S'applique uniquement quand le nombre de connexions est supérieur à minimumIdle

maxLifetime

  • Définition : Durée de vie maximale (en millisecondes) d'une connexion dans le pool, qu'elle soit active ou inactive.
  • Valeur par défaut : 1 800 000 ms (30 minutes)
  • Objectif : Éviter les problèmes liés aux connexions trop anciennes (fuites de mémoire, déconnexions côté serveur)
  • Comportement : S'applique à toutes les connexions, indépendamment de leur état d'utilisation

Lien entre les deux paramètres

  1. Hiérarchie d'application :

    • maxLifetime est toujours prioritaire sur idleTimeout
    • Une connexion sera fermée si elle atteint sa durée de vie maximale, même si elle n'a pas atteint son temps d'inactivité
  2. Complémentarité :

    • idleTimeout gère l'efficacité des ressources en période de faible charge
    • maxLifetime gère la stabilité et la sécurité du pool sur la durée
  3. Recommandations pratiques :

    • maxLifetime devrait toujours être inférieur au timeout de la base de données
    • idleTimeout devrait être configuré en fonction des patterns d'utilisation de l'application

Par exemple, si une connexion est inactive depuis 8 minutes et que idleTimeout est de 10 minutes, mais que cette connexion a une durée de vie totale de 29 minutes avec maxLifetime à 30 minutes, c'est maxLifetime qui déclenchera sa fermeture en premier.

Ces deux paramètres travaillent ensemble pour maintenir un pool de connexions sain, en équilibrant la disponibilité immédiate des connexions, l'utilisation des ressources et la fiabilité du système.

Un bon exemple est expliqué ici :

For example, let’s assume the value of maximum-pool-size is 8 and minimum-idle is 4. The Spring Boot application needs to perform database operations for 6 concurrent threads. Thus, the Spring Boot application establishes 6 actual connections with the database. When all the database-related tasks are performed for the 6 threads, there will be 6 idle connections in the pool. Now, HikariCP will check what is the value of the minimum-idle property and try to adjust the number of idle connections in the pool. In this example, the value of minimum-idle is 4, so HikariCP will remove 2 idle connections from the pool to make the number of idle connections equal to 4. But before removing the required number of idle connections from the pool, HikariCP will keep those extra connections in the pool till the time given in the idle-timeout. A connection will never be retired as idle before this timeout. A value of 0 means that idle connections are never removed from the pool. The minimum allowed value is 10000 milliseconds (10 seconds). The default value is 600000 milliseconds (10 minutes).

PS : Fermeture de connexion physique et non physique

L'appel à la méthode connection.close() dans le contexte d'un pool de connexions comme HikariCP ne ferme pas réellement la connexion physique à la base de données.

Voici ce qui se passe réellement lorsque vous appelez connection.close() :

  1. La connexion n'est pas fermée physiquement - HikariCP intercepte cet appel et, au lieu de fermer la connexion TCP/IP avec la base de données, il :

    • Remet à zéro l'état de la connexion (rollback des transactions non validées, fermeture des statements ouverts)
    • Marque la connexion comme "inactive" ou "disponible"
    • Replace la connexion dans le pool pour qu'elle puisse être réutilisée
  2. C'est un "retour au pool" - C'est pour cette raison qu'on dit souvent que l'on "retourne" la connexion au pool plutôt que de la "fermer"

  3. Cas où la connexion peut être vraiment fermée :

    • Si la connexion a dépassé son maxLifetime
    • Si un problème a été détecté sur la connexion
    • Si le pool est en train d'être fermé (shutdown)

C'est justement ce mécanisme qui permet au pool de connexions d'être efficace : les connexions physiques coûteuses à établir sont maintenues ouvertes et réutilisées, tandis que le code applicatif peut continuer à utiliser l'API JDBC standard avec connection.close() sans avoir à se préoccuper de la gestion du pool.

Impact des connexions inactives sur les performances et ressources

Les connexions inactives dans un pool comme HikariCP ont plusieurs impacts sur les performances et la consommation de ressources :

Consommation de ressources

  1. Mémoire côté application :

    • Chaque connexion inactive consomme de la mémoire (généralement 0,5 à 2 Mo par connexion)
    • Structures internes, buffers, métadonnées de session
  2. Ressources côté serveur de base de données :

    • Chaque connexion, même inactive, maintient un processus/thread sur le serveur
    • Consommation de mémoire pour les buffers, caches de requêtes, etc.
    • Utilisation de descripteurs de fichiers et sockets réseau
  3. Trafic réseau de maintenance :

    • Paquets keep-alive pour maintenir les connexions TCP ouvertes
    • Requêtes de validation si keepaliveTime est configuré

Impact sur les performances

  1. Avantages :

    • Disponibilité immédiate pour traiter des pics de charge
    • Élimination du temps d'établissement des connexions (souvent 10-100ms)
    • Stabilité des temps de réponse (moins de variations)
  2. Inconvénients :

    • Saturation possible des ressources serveur avec trop de connexions
    • Risque de dépassement des limites du serveur de base de données
    • Contention potentielle sur les ressources partagées

Équilibre optimal

Le paramétrage idéal dépend de plusieurs facteurs :

  • Charge de l'application : Nombre et fréquence des requêtes
  • Capacité du serveur de base de données : Nombre maximal de connexions supportées
  • Pattern d'utilisation : Stable vs pics de charge importants

Un trop grand nombre de connexions inactives peut diminuer les performances globales du système en consommant inutilement des ressources, tandis qu'un nombre trop faible peut entraîner des latences lors des pics de charge.

Recommandations pratiques

  • Configurez minimumIdle inférieur à maximumPoolSize pour les applications à trafic variable
  • Surveillez les métriques de votre pool (taux d'utilisation, temps d'attente)
  • Pour les petites applications, commencez avec des valeurs modestes (5-10 connexions maximum)
  • Pour les applications à forte charge, faites des tests de charge pour déterminer les valeurs optimales

L'objectif est d'avoir juste assez de connexions inactives pour gérer les variations normales de charge, sans gaspiller de ressources.

Aucun commentaire:

Enregistrer un commentaire