Retour au blog
Performances & Benchmarks · 9 min de lecture

Combien de connexions Postgres sans pooler : max_connections, formule et tuning

Affane Daylami · Fondateur· 24 août 2026

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.

L’essentiel
  • 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.
#
Diagnostic

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 que ça change en pratique
Une application qui ouvre 500 connexions directes vers Postgres sans pooler force le serveur à gérer 500 processus système simultanés, même si l’immense majorité d’entre elles restent inactives entre deux requêtes.
#
Coût mémoire

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).

Le vrai pire cas à retenir
Ce n’est pas max_connections × work_mem tout seul qui menace la mémoire d’un serveur. C’est max_connections × work_mem × nombre d’opérations simultanées par requête. C’est ce produit qui explique un serveur qui swap, ou qui tombe en manque de mémoire après une augmentation de max_connections jugée anodine.
#
Formule

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).

La formule
concurrence active idéale = (cœurs physiques × 2) + disques efficaces. Le nombre de cœurs exclut l’hyperthreading. Le nombre de disques efficaces reste proche de 1 sur un stockage SSD moderne, où la notion de disque physique séparé (« spindle ») perd une bonne partie de son sens d’origine.

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).

#
Procédure

Comment changer max_connections (et pourquoi un redémarrage est obligatoire)

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 :

psql
SQL
SHOW max_connections;
SELECT name, setting, context
FROM pg_settings
WHERE name = 'max_connections';
-- context = 'postmaster' confirme qu'un redémarrage est requis

Appliquez ensuite la nouvelle valeur, puis redémarrez :

psql
SQL
ALTER SYSTEM SET max_connections = '300';
-- Écrit dans postgresql.auto.conf.
-- Sans effet tant que Postgres n'a pas redémarré.
terminal
BASH
# Avec systemd
sudo systemctl restart postgresql
# Sans systemd, directement avec pg_ctl
pg_ctl restart -D $PGDATA -m fast
Une marge que beaucoup oublient
max_connections inclut par défaut 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.
#
Vérifié dans le code

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).

100
DÉFAUT POSTGRES
max_connections avant tout tuning
50→400
PALIERS DÉDIÉS AURABASE
free à enterprise, par cluster CNPG
3
RÉSERVÉES SUPERUSER
superuser_reserved_connections, défaut Postgres

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 :

free (dédié)max_connections 501 instance · 500m vCPU · 512Mi
pro (défaut)max_connections 2002 instances · 1 vCPU · 2Gi
teammax_connections 3003 instances · 2 vCPU · 3Gi
enterprisemax_connections 4003 instances · 2 vCPU · 4Gi

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é :

freemax_connections 50max_client_conn 100max_user_connections 20
promax_connections 100max_client_conn 200max_user_connections 60
teammax_connections 200max_client_conn 400max_user_connections 150

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.

Chiffres en cours de calibration, assumé comme tel
Le code documente explicitement ces budgets de pooler mutualisé comme des valeurs de départ à calibrer en conditions réelles, en mesurant 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.
#
Méthode

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é.

  1. 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.
  2. 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.
  3. Fixez max_connections au-dessus du besoin réel de l’étape 1, avec une marge pour superuser_reserved_connections et pour tout outil d’administration qui ouvre ses propres connexions en dehors de l’application.
  4. 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.
  5. 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 :

psql
SQL
SELECT state, count(*)
FROM pg_stat_activity
GROUP BY state
ORDER BY count(*) DESC;
#
Signal d’alerte

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.

  1. L’erreur FATAL: sorry, too many clients already apparaît en pic de charge, alors que la majorité des connexions affichées par pg_stat_activity sont à l’état idle.
  2. 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.
  3. 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.

#
Questions Fréquentes

FAQ

Quel est le max_connections par défaut de PostgreSQL ?+
100, avec 3 connexions réservées au superutilisateur par défaut (superuser_reserved_connections). Ce défaut convient à beaucoup d’applications qui passent par un pooler, mais devient vite insuffisant sans pooler dès qu’une flotte de processus applicatifs ouvre chacun son propre lot de connexions.
Peut-on changer max_connections sans redémarrer PostgreSQL ?+
Non. max_connections est un paramètre de contexte postmaster : Postgres le lit une seule fois au démarrage pour dimensionner sa mémoire partagée. ALTER SYSTEM SET écrit la nouvelle valeur dans postgresql.auto.conf, mais seul un redémarrage complet du serveur l’applique ; un reload ou un SIGHUP ne suffisent pas.
Combien de mémoire consomme une connexion PostgreSQL inactive ?+
Il n’existe pas de chiffre officiel unique : cela dépend de work_mem, de shared_buffers et des extensions chargées par session. Ce qui est documenté, en revanche, c’est que work_mem s’alloue par opération de tri ou de hachage dans une requête, pas par connexion : une seule requête complexe peut donc consommer plusieurs fois work_mem sur une seule connexion active.
Faut-il toujours préférer un pooler comme PgBouncer à un max_connections plus élevé ?+
Dans la majorité des cas, oui, dès que le nombre de connexions clientes réelles dépasse largement la concurrence idéale calculée par la formule du wiki PostgreSQL. Un pooler en mode transaction mutualise un petit nombre de connexions physiques entre un nombre bien plus grand de connexions logiques côté application. Voir notre comparatif PgBouncer, Supavisor et PgCat pour choisir lequel.
Que mesure exactement la formule (cœurs × 2) + disques efficaces ?+
Elle estime la concurrence active idéale : le nombre de requêtes que le CPU et le disque d’un serveur donné peuvent traiter en parallèle sans dégrader le débit, pas le nombre total de connexions à ouvrir dans max_connections. C’est un point de départ à valider par la mesure, documenté par le wiki officiel du projet PostgreSQL, pas une limite dure.
Comment savoir si mon serveur Postgres est proche de sa limite de connexions ?+
Interrogez pg_stat_activity et comparez le nombre de connexions à l’état active à celui à l’état idle. Un grand nombre de connexions idle proches du plafond max_connections, sans requête active derrière, indique presque toujours un besoin de pooler plutôt qu’un besoin de relever encore max_connections.
TUNING POSTGRES

Testez votre propre budget de connexions sur un projet Aurabase.

Créez un projet gratuit avec Postgres 16 dédié et un pooler PgBouncer déjà en place devant le SDK. Ajustez max_connections vous-même, pg_stat_activity sous les yeux.

Aucune carte bancaire requise · 500 MB gratuits · 50 000 MAU