Combien de connexions Postgres sans pooler : max_connections, formule et tuning
Sans pooler devant votre serveur, max_connections doit couvrir chaque connexion cliente ouverte en même temps, pas le nombre de requêtes que Postgres peut traiter efficacement en parallèle. Confondre ces deux chiffres est la cause la plus fréquente d’un max_connections mal réglé : trop bas pour absorber la charge, ou trop haut pour la mémoire réellement disponible.
Cet article donne la formule publiée par le wiki PostgreSQL pour calculer la concurrence idéale de votre matériel (le connection pool sizing formula le plus cité de l’écosystème), explique pourquoi chaque connexion coûte plus cher qu’un thread applicatif, puis détaille la procédure pour fixer max_connections sans deviner. Notre méthodologie de benchmark documente le protocole de mesure utilisé pour toute affirmation de performance sur ce blog.
- Sans pooler, max_connections doit couvrir toutes les connexions clientes simultanées, pas seulement celles que Postgres peut traiter efficacement en parallèle.
- Formule de référence du wiki PostgreSQL : concurrence active idéale = (cœurs physiques × 2) + disques efficaces. Un point de départ à valider par la mesure, pas une limite dure.
- max_connections est un paramètre de contexte
postmaster: le changer exige un redémarrage complet du serveur, pas un simple reload. - Chaque connexion Postgres est un processus système distinct, pas un thread léger : c’est ce qui rend l’overhead réel dès que le nombre de connexions grimpe.
- Vérifié dans le code : sur ses clusters Postgres dédiés, Aurabase fait varier max_connections de 50 (palier free) à 400 (palier enterprise) selon la taille du cluster.
Pourquoi une connexion Postgres coûte plus cher qu’un thread applicatif
Postgres n’utilise pas un pool de threads légers pour ses connexions. Chaque connexion cliente déclenche un processus système à part entière.
Le processus postmaster en crée un nouveau (« fork ») pour chaque tentative de connexion, dédié à cette seule session jusqu’à sa fermeture. La documentation officielle du projet décrit précisément ce mécanisme dans son chapitre sur les fondamentaux d’architecture (postgresql.org/docs/current/connect-estab.html, section « Connection Semantics », consulté le 24 août 2026).
Ce choix a un avantage réel : un plantage sur une connexion n’affecte pas les autres, chaque processus étant isolé du reste du serveur. Il a aussi un coût direct : chaque connexion supplémentaire ajoute un processus OS complet à ordonnancer, avec son propre espace mémoire et son propre overhead de changement de contexte pour le noyau.
Ce qu’une connexion consomme réellement : mémoire partagée et work_mem
Deux mécanismes distincts pèsent sur la mémoire, et les confondre mène presque toujours à un mauvais diagnostic.
Le premier est fixe. Au démarrage, Postgres réserve des structures de mémoire partagée (verrous, table des processus) dimensionnées sur la valeur de max_connections, que ces connexions soient ouvertes ou non ensuite. La documentation officielle du paramètre le signale explicitement : l’augmenter peut demander plus de mémoire partagée système que la configuration par défaut de votre OS n’en autorise (postgresql.org/docs/current/runtime-config-connection.html, consulté le 24 août 2026).
Le second est variable, et bien plus dangereux à l’échelle : work_mem ne s’alloue pas une fois par connexion, mais une fois par opération de tri ou de hachage dans le plan d’une requête. La documentation officielle est explicite sur ce point : une requête complexe peut lancer plusieurs de ces opérations en parallèle, et plusieurs sessions peuvent en faire autant simultanément, si bien que la mémoire réellement utilisée peut valoir plusieurs fois work_mem (postgresql.org/docs/current/runtime-config-resource.html, consulté le 24 août 2026).
La formule de dimensionnement du wiki PostgreSQL
Le wiki officiel du projet PostgreSQL documente une formule de référence pour calculer combien de connexions actives votre matériel peut traiter efficacement en parallèle, pas combien de connexions ouvrir au total (wiki.postgresql.org/wiki/Number_Of_Database_Connections, consulté le 24 août 2026).
Sur un serveur à 8 cœurs physiques et un stockage SSD, la formule donne (8 × 2) + 1 = 17 connexions actives avant que le débit ne commence à se dégrader. Ce chiffre surprend souvent : il paraît minuscule face aux centaines de connexions qu’une application ouvre en pratique. C’est précisément le sujet du paragraphe suivant.
Le nombre calculé par la formule mesure la concurrence que le CPU et le disque peuvent absorber, pas le nombre de connexions clientes que votre application a besoin d’ouvrir. Une flotte de 20 processus applicatifs, chacun avec son propre pool de 10 connexions, ouvre 200 connexions simultanées vers Postgres même si 17 d’entre elles seulement travaillent activement à un instant donné. Sans pooler, max_connections doit couvrir les 200, pas les 17. C’est cet écart qui pousse la plupart des architectures à ajouter un pooler en mode transaction, quitte à choisir ensuite lequel (voir notre comparatif PgBouncer, Supavisor et PgCat).
max_connections ne se change pas à chaud. C’est un paramètre de contexte postmaster : Postgres le lit une seule fois, au démarrage, pour dimensionner sa mémoire partagée. Un rechargement de configuration (pg_reload_conf() ou SIGHUP) ne suffit pas, il faut redémarrer le serveur.
Vérifiez d’abord la valeur actuelle et son contexte, pour confirmer qu’un redémarrage sera bien nécessaire :
Appliquez ensuite la nouvelle valeur, puis redémarrez :
superuser_reserved_connections (3 par défaut) : ces connexions sont réservées à un superutilisateur en cas de saturation, elles ne sont jamais disponibles pour votre application, même si le compteur global n’est pas encore atteint.Comment Aurabase budgète max_connections sur ses clusters Postgres
Le dimensionnement de max_connections n’est pas qu’un exercice théorique. Voici comment Aurabase le budgète sur ses propres clusters Postgres tenant, vérifié directement dans le dépôt au moment de la rédaction (deploy/cnpg/tenant-cluster.yaml, deploy/cnpg/tenant-pooler.yaml, services/aura-provisioner/src/k8s_tenant.rs).
Clusters dédiés : un cluster Postgres par projet
Sur ce palier (voir notre comparatif base dédiée vs mutualisée), chaque projet reçoit son propre cluster CloudNativePG et son propre budget max_connections, dimensionné avec la taille de l’instance :
Clusters mutualisés : plusieurs projets d’une organisation, un budget partagé
Sur ce second chemin, tous les projets d’une même organisation se connectent via un pooler CNPG (PgBouncer, mode transaction) devant un primaire mutualisé :
Tous les projets d’une organisation se connectent par un rôle applicatif partagé. max_user_connections plafonne donc, à lui seul, le total de connexions serveur que ce rôle peut ouvrir sur tout le cluster : c’est le vrai garde-fou cluster-global, pas max_client_conn, qui ne limite que les connexions clientes vers le pooler lui-même.
Ce pooler ne sert cependant que le trafic applicatif du SDK. PostgREST, lui, reste branché en direct sur le primaire (service -rw) : le pooling en mode transaction casserait son mécanisme de rechargement de schéma, qui écoute un canal LISTEN dédié nommé pgrst. Ses propres connexions (2 par réplica sur le palier mutualisé, 10 par réplica sur le palier dédié) comptent donc directement dans le budget max_connections du primaire, en dehors de tout pooler, exactement le genre de connexion « oubliée » que l’étape 1 de la procédure ci-dessous doit inclure.
pg_stat_activity sous charge, pas comme des chiffres figés issus d’un benchmark publié. C’est la même discipline que celle décrite dans notre méthodologie de benchmark : mesurer avant d’ajuster, pas deviner puis espérer. Ces clusters tournent sur PostgreSQL 16, un choix documenté dans notre comparatif Postgres 16 vs 17 vs 18.La procédure en 5 étapes pour dimensionner max_connections sans pooler
Cette procédure ne dépend d’aucun outil particulier : elle s’applique à n’importe quel serveur Postgres, géré ou auto-hébergé.
- Comptez vos connexions clientes réelles. Nombre de processus applicatifs multiplié par la taille de leur pool interne, plus les outils d’administration, la réplication et le monitoring. C’est ce chiffre, pas la formule, qui fixe le plancher de max_connections.
- Calculez la concurrence idéale de votre matériel avec la formule du wiki PostgreSQL : (cœurs physiques × 2) + disques efficaces. Ce chiffre indique combien de ces connexions peuvent réellement travailler en parallèle sans dégrader le débit.
- Fixez max_connections au-dessus du besoin réel de l’étape 1, avec une marge pour
superuser_reserved_connectionset pour tout outil d’administration qui ouvre ses propres connexions en dehors de l’application. - Appliquez le changement avec ALTER SYSTEM SET, puis redémarrez le serveur. C’est un paramètre postmaster : un simple reload ne suffit pas, comme détaillé plus haut.
- Surveillez pg_stat_activity dans la durée. Si le nombre de connexions idle dépasse largement celui des connexions active, ce n’est pas un problème de max_connections : c’est le signal qu’il vous faut un pooler devant le serveur, pas un chiffre plus haut.
La requête de surveillance de l’étape 5, directement exploitable :
Quand la formule ne suffit plus : les signes qu’il vous faut un pooler
Trois signaux reviennent systématiquement quand max_connections seul ne suffit plus, quelle que soit sa valeur.
- L’erreur
FATAL: sorry, too many clients alreadyapparaît en pic de charge, alors que la majorité des connexions affichées parpg_stat_activitysont à l’état idle. - L’application tourne en environnement serverless ou avec des workers éphémères (fonctions edge, jobs courts), qui ouvrent et ferment des connexions bien plus vite que le modèle processus-par-connexion de Postgres n’a été conçu pour absorber.
- La formule et la procédure ci-dessus ont déjà été appliquées, et le besoin réel en connexions clientes continue de dépasser ce que la mémoire disponible permet d’allouer sans mettre en danger work_mem ou shared_buffers.
Dans ces trois cas, la bonne réponse est presque toujours un pooler positionné entre l’application et Postgres, pas un max_connections plus élevé. Notre comparatif PgBouncer, Supavisor et PgCat détaille les trois options, et notre guide sur le mode transaction explique le compromis le plus fréquent une fois le pooler en place. Pour l’ensemble des réglages Postgres au-delà des connexions, voir notre checklist de tuning Postgres en production.
Comment changer max_connections (et pourquoi un redémarrage est obligatoire)