Dit artikel geeft de formule die is gepubliceerd door de PostgreSQL-wiki om de ideale gelijktijdigheid van uw hardware te berekenen (de meest geciteerde formule voor de grootte van de verbindingspool in het ecosysteem), legt uit waarom elke verbinding meer kost dan een applicatiethread en beschrijft vervolgens de procedure voor het instellen van max_connections zonder te raden. Onze benchmarkmethodologie documenteert het meetprotocol dat wordt gebruikt voor eventuele prestatieclaims op deze blog.
De essentie
- Zonder pooling zouden max_connections alle gelijktijdige clientverbindingen moeten dekken, niet alleen de verbindingen die Postgres efficiënt parallel kan verwerken.
- PostgreSQL-wiki-referentieformule: ideale actieve gelijktijdigheid = (fysieke kernen × 2) + efficiënte schijven. Een uitgangspunt dat gevalideerd moet worden door meting, geen harde grens.
- max_connections is een contextparameter
postmaster: het wijzigen ervan vereist een volledige herstart van de server, niet eenvoudigweg opnieuw laden. - Elke Postgres-verbinding is een afzonderlijk systeemproces en geen lichtgewicht draad: dit maakt de overhead reëel zodra het aantal verbindingen stijgt.
- Geverifieerd in de code: op zijn speciale Postgres-clusters varieert Aurabase max_connections van 50 (gratis laag) tot 400 (ondernemingslaag), afhankelijk van de grootte van het cluster.
Waarom een Postgres-verbinding meer kost dan een applicatiethread
Postgres gebruikt geen lichtgewicht threadpool voor zijn verbindingen. Elke clientverbinding activeert een volwaardig systeemproces.
Het postmaster-proces maakt voor elke verbindingspoging een nieuwe (“fork”), bestemd voor deze enkele sessie totdat deze wordt gesloten. De officiële projectdocumentatie beschrijft precies dit mechanisme in het hoofdstuk over architecturale grondbeginselen (postgresql.org/docs/current/connect-estab.html, sectie “Verbindingssemantiek”, geraadpleegd op 24 augustus 2026).
Deze keuze heeft een echt voordeel: een crash op één verbinding heeft geen invloed op de andere, omdat elk proces geïsoleerd is van de rest van de server. Er zijn ook directe kosten aan verbonden: elke extra verbinding voegt een heel OS-proces toe aan de planning, met zijn eigen geheugenruimte en zijn eigen context-switching-overhead voor de kernel.
Een applicatie die 500 directe verbindingen met Postgres opent zonder te poolen, dwingt de server om 500 gelijktijdige systeemprocessen te beheren, zelfs als de overgrote meerderheid daarvan inactief blijft tussen twee verzoeken.
Wat een verbinding feitelijk verbruikt: gedeeld geheugen en work_mem
Er zijn twee verschillende mechanismen die van invloed zijn op het geheugen, en het verwarren ervan leidt bijna altijd tot een verkeerde diagnose.
De eerste staat vast. Bij het opstarten reserveert Postgres gedeelde geheugenstructuren (sloten, procestabel) op basis van de waarde van max_connections, ongeacht of deze verbindingen vervolgens wel of niet worden geopend. De officiële documentatie voor de instelling wijst er expliciet op: het verhogen ervan kan meer gedeeld systeemgeheugen vereisen dan de standaardconfiguratie van uw besturingssysteem toestaat (postgresql.org/docs/current/runtime-config-connection.html, geraadpleegd op 24 augustus 2026).
De tweede is variabel en veel gevaarlijker op schaal: work_mem wordt niet één keer per verbinding toegewezen, maar één keer per sorteer- of hashbewerking in het queryplan. De officiële documentatie is expliciet op dit punt: een complexe query kan verschillende van deze bewerkingen parallel starten, en verschillende sessies kunnen hetzelfde tegelijkertijd doen, zodat het werkelijk gebruikte geheugen meerdere malen de moeite waard kan zijn work_mem (postgresql.org/docs/current/runtime-config-resource.html, geraadpleegd op 24 augustus 2026).
Het zijn niet alleen max_connections × work_mem die het geheugen van een server bedreigen. Het is max_connections × work_mem × aantal gelijktijdige bewerkingen per query. Het is dit product dat een server verklaart die verwisselt, of die geen geheugen meer heeft na een toename van het aantal max_connections dat als onschadelijk wordt beschouwd.
De formule voor de grootte van de PostgreSQL-wiki
De officiële PostgreSQL-projectwiki documenteert een benchmarkformule voor het berekenen van hoeveel actieve -verbindingen uw hardware efficiënt parallel kan verwerken, niet hoeveel verbindingen er in totaal geopend zijn (wiki.postgresql.org/wiki/Number_Of_Database_Connections, geraadpleegd op 24 augustus 2026).
ideale actieve gelijktijdigheid = (fysieke kernen × 2) + efficiënte schijven. Het aantal cores is exclusief hyperthreading. Het aantal effectieve schijven blijft dichtbij 1 op moderne SSD-opslag, waar het idee van een afzonderlijke fysieke schijf (“spindel”) veel van zijn oorspronkelijke betekenis verliest.
Op een server met 8 fysieke cores en SSD-opslag geeft de formule (8 × 2) + 1 = 17 actieve verbindingen voordat de doorvoer begint af te nemen. Dit cijfer is vaak verrassend: het lijkt klein vergeleken met de honderden verbindingen die een applicatie in de praktijk opent. Dit is precies het onderwerp van de volgende paragraaf.
Het getal dat door de formule wordt berekend, meet de gelijktijdigheid die de CPU en schijf kunnen absorberen, niet het aantal clientverbindingen dat uw toepassing moet openen. Een vloot van 20 applicatieprocessen, elk met een eigen pool van 10 verbindingen, opent 200 gelijktijdige verbindingen met Postgres, zelfs als er op een bepaald moment slechts 17 daarvan actief werken. Zonder een pooler moeten max_connections de 200 dekken, niet de 17. Het is deze kloof die de meeste architecturen ertoe aanzet een pooler toe te voegen in transactiemodus, zelfs als dit betekent dat ze moeten kiezen welke (zie onze vergelijking PgBouncer, Supavisor en PgCat).
Hoe max_connections te veranderen (en waarom opnieuw opstarten vereist is)
max_connections is niet hot-swap. Dit is een contextparameter postmaster: Postgres leest deze één keer bij het opstarten, om het gedeelde geheugen te vergroten. Het opnieuw laden van de configuratie (pg_reload_conf() of SIGHUP) is niet voldoende; u moet de server opnieuw opstarten.
Controleer eerst de huidige waarde en de context ervan, om te bevestigen dat een herstart noodzakelijk is:
Pas vervolgens de nieuwe waarde toe en start vervolgens opnieuw:
max_connections bevat standaard superuser_reserved_connections (standaard 3): deze verbindingen zijn gereserveerd voor een superuser in geval van verzadiging, ze zijn nooit beschikbaar voor uw toepassing, zelfs als de globale teller nog niet is bereikt.
Hoe Aurabase max_connections budgetteert op zijn Postgres-clusters
Het dimensioneren van max_connections is niet alleen een theoretische oefening. Hier ziet u hoe Aurabase het budgetteert op de beheerde Postgres-clusters:
Toegewijde clusters: één Postgres-cluster per project
Op dit niveau (zie onze vergelijking dedicated versus gedeelde basis), ontvangt elk project zijn eigen CloudNativePG-cluster en zijn eigen max_connections budget, afgestemd op de grootte van de instantie:
| gratis (speciaal) | max_verbindingen 50 | 1 exemplaar · 500m vCPU · 512Mi |
|---|---|---|
| pro (standaard) | max_verbindingen 200 | 2 instanties · 1 vCPU · 2Gi |
| team | max_verbindingen 300 | 3 instanties · 2 vCPU · 3Gi |
| zaken | max_verbindingen 400 | 3 instanties · 2 vCPU · 4Gi |
Gedeelde clusters: meerdere projecten van een organisatie, een gedeeld budget
Op dit tweede pad zijn alle projecten van dezelfde organisatie verbonden via een CNPG-pooler (PgBouncer, transaction-modus) voor een gedeelde primaire:
| gratis | max_verbindingen 50 | max_client_conn 100 | max_user_connections 20 |
|---|---|---|---|
| pro | max_verbindingen 100 | max_client_conn 200 | max_user_connections 60 |
| team | max_verbindingen 200 | max_client_conn 400 | max_user_connections 150 |
Alle projecten in een organisatie zijn met elkaar verbonden via een gedeelde applicatierol. max_user_connections alleen al beperkt daarom het totale aantal serververbindingen dat deze rol in het hele cluster kan openen: dit is de echte cluster-globale beveiliging, niet max_client_conn, die alleen clientverbindingen met de pooler zelf beperkt.
Deze pooler bedient echter alleen SDK-applicatieverkeer. PostgREST blijft op zijn beurt rechtstreeks verbonden met de primaire (-rwservice): poolen in de transactiemodus zou het herlaadmechanisme van het schema verbreken, dat luistert naar een speciaal LISTEN kanaal met de naam pgrst. De eigen verbindingen (2 per replica op het gedeelde niveau, 10 per replica op het speciale niveau) tellen daarom direct mee in het max_connections budget van de primaire, buiten welke pooler dan ook, precies het soort “vergeten” verbinding dat stap 1 van de onderstaande procedure moet omvatten.
De code documenteert deze gedeelde poolerbudgetten expliciet als startwaarden die in reële omstandigheden moeten worden gekalibreerd, door pg_stat_activity onder belasting te meten, en niet als vaste cijfers uit een gepubliceerde benchmark. Dit is dezelfde discipline als beschreven in onze benchmarkmethodologie: meten voordat je bijstelt, niet raden en dan hopen. Deze clusters draaien op PostgreSQL 16, een keuze die is gedocumenteerd in onze Postgres 16 versus 17 versus 18vergelijking.
De procedure in 5 stappen om max_connections op maat te maken zonder pooling
Deze procedure is niet afhankelijk van een bepaald hulpmiddel: het is van toepassing op elke Postgres-server, beheerd of zelfgehost.
- Tel uw daadwerkelijke clientverbindingen. Aantal applicatieprocessen vermenigvuldigd met de grootte van hun interne pool, plus beheertools, replicatie en monitoring. Het is dit getal, en niet de formule, dat de max_connections-vloer bepaalt.
- Bereken de ideale gelijktijdigheid van uw hardware met de formule uit de PostgreSQL-wiki: (fysieke kernen × 2) + efficiënte schijven. Dit cijfer geeft aan hoeveel van deze verbindingen feitelijk parallel kunnen werken zonder de doorvoer te verminderen.
- Stel max_connections in boven de daadwerkelijke behoefte voor stap 1, met marge voor
superuser_reserved_connectionsen voor eventuele beheerderstools die hun eigen verbindingen buiten de applicatie openen. - Pas de wijziging toe met ALTER SYSTEM SET en start vervolgens de server opnieuw op. Dit is een postmasterparameter: eenvoudig herladen is niet voldoende, zoals hierboven beschreven.
- Controleer pg_stat_activiteit in de loop van de tijd. Als het aantal inactieve verbindingen het aantal actieve verbindingen aanzienlijk overschrijdt, is dit geen max_connections-probleem: het is het signaal dat u een pooler vóór de server nodig heeft, niet een hoger aantal.
Het monitoringverzoek uit stap 5, direct bruikbaar:
Wanneer de formule niet langer genoeg is: de signalen dat je een pooler nodig hebt
Drie signalen keren systematisch terug wanneer max_connections alleen niet langer voldoende is, wat de waarde ervan ook is.
- De
FATAL: sorry, too many clients already-fout verschijnt tijdens piekbelasting, terwijl de meerderheid van de verbindingen die doorpg_stat_activityworden weergegeven zich in de inactieve status bevinden. - De applicatie draait in een serverloze omgeving of met tijdelijke werkers (edge-functies, korte taken), die verbindingen veel sneller openen en sluiten dan waarvoor het proces-per-verbinding-model van Postgres was ontworpen.
- De bovenstaande formule en procedure zijn al toegepast, en de werkelijke behoefte aan clientverbindingen blijft groter dan wat het beschikbare geheugen kan toewijzen zonder werkgeheugen of gedeelde buffers in gevaar te brengen.
In deze drie gevallen is het juiste antwoord bijna altijd een pooler die tussen de applicatie en Postgres is geplaatst, en niet een hogere max_connections. Onze vergelijking PgBouncer, Supavisor en PgCat beschrijft de drie opties, en onze gids voor transactiemodus legt de meest voorkomende compromissen uit zodra de pooler eenmaal is geïnstalleerd. Voor alle Postgres-tuning die verder gaat dan verbindingen, zie onze productie Postgres-tuningchecklist.