W tym artykule przedstawiono formułę opublikowaną na wiki PostgreSQL do obliczenia idealnej współbieżności sprzętu (najczęściej cytowana formuła rozmiaru puli połączeń w ekosystemie), wyjaśnia, dlaczego każde połączenie kosztuje więcej niż wątek aplikacji, a następnie szczegółowo opisuje procedurę ustawiania max_connections bez zgadywania. Nasza metodologia testów porównawczych dokumentuje protokół pomiarowy używany do wszelkich oświadczeń dotyczących wydajności na tym blogu.
Najważniejsze
- Bez łączenia, max_connections powinien obejmować wszystkie równoczesne połączenia klientów, a nie tylko te, które Postgres może efektywnie przetwarzać równolegle.
- Formuła referencyjna wiki PostgreSQL: idealna aktywna współbieżność = (rdzenie fizyczne × 2) + wydajne dyski. Punkt wyjścia, który należy potwierdzić pomiarem, a nie sztywny limit.
- max_connections to parametr kontekstowy
postmaster: zmiana go wymaga całkowitego ponownego uruchomienia serwera, a nie prostego ponownego załadowania. - Każde połączenie Postgres jest oddzielnym procesem systemowym, a nie lekkim wątkiem: to właśnie sprawia, że obciążenie staje się realne, gdy tylko liczba połączeń wzrośnie.
- Zweryfikowano w kodzie: w dedykowanych klastrach Postgres Aurabase zmienia max_connections od 50 (warstwa bezpłatna) do 400 (warstwa korporacyjna) w zależności od rozmiaru klastra.
Dlaczego połączenie Postgres kosztuje więcej niż wątek aplikacji
Postgres nie używa lekkiej puli wątków do swoich połączeń. Każde połączenie klienta uruchamia pełnoprawny proces systemowy.
Proces postmaster tworzy nowy („fork”) dla każdej próby połączenia, dedykowany tej pojedynczej sesji, aż do jej zamknięcia. Oficjalna dokumentacja projektu opisuje dokładnie ten mechanizm w rozdziale poświęconym podstawom architektury (postgresql.org/docs/current/connect-estab.html, sekcja „Connection Semantics”, dostęp 24 sierpnia 2026).
Wybór ten ma realną zaletę: awaria jednego połączenia nie wpływa na pozostałe, a każdy proces jest odizolowany od reszty serwera. Ma to również bezpośredni koszt: każde dodatkowe połączenie dodaje do harmonogramu cały proces systemu operacyjnego, z własną przestrzenią pamięci i własnym narzutem na przełączanie kontekstu dla jądra.
Aplikacja, która otwiera 500 bezpośrednich połączeń do Postgres bez łączenia, zmusza serwer do zarządzania 500 jednoczesnymi procesami systemowymi, nawet jeśli zdecydowana większość z nich pozostaje bezczynna pomiędzy dwoma żądaniami.
Co faktycznie zużywa połączenie: pamięć współdzielona i work_mem
Na pamięć wpływają dwa różne mechanizmy, a pomieszanie ich prawie zawsze prowadzi do błędnej diagnozy.
Pierwsza jest ustalona. Podczas uruchamiania Postgres rezerwuje struktury pamięci współdzielonej (blokady, tabelę procesów) o rozmiarze dostosowanym do wartości max_connections, niezależnie od tego, czy połączenia te zostaną później otwarte, czy nie. Oficjalna dokumentacja tego ustawienia wyraźnie wskazuje: zwiększenie go może wymagać większej ilości pamięci współdzielonej systemu, niż pozwala na to domyślna konfiguracja systemu operacyjnego (postgresql.org/docs/current/runtime-config-connection.html, dostęp: 24 sierpnia 2026 r.).
Drugi jest zmienny i znacznie bardziej niebezpieczny na dużą skalę: work_mem nie jest przydzielany raz na połączenie, ale raz na operację sortowania lub mieszania w planie zapytań. Oficjalna dokumentacja jasno określa tę kwestię: złożone zapytanie może uruchomić kilka z tych operacji równolegle, a kilka sesji może wykonać to samo jednocześnie, tak że faktycznie używana pamięć może być kilkukrotnie warta work_mem (postgresql.org/docs/current/runtime-config-resource.html, dostęp 24 sierpnia 2026).
Nie samo max_connections × work_mem zagraża pamięci serwera. Jest to max_connections × work_mem × liczba równoczesnych operacji na zapytanie. To właśnie ten produkt wyjaśnia serwer, który się zamienia lub któremu kończy się pamięć po wzroście max_connections uważanym za nieszkodliwy.
Formuła rozmiaru wiki PostgreSQL
Oficjalna wiki projektu PostgreSQL dokumentuje wzór wzorcowy do obliczania liczby aktywnych połączeń, które Twój sprzęt może wydajnie przetwarzać równolegle, a nie liczby otwartych połączeń w sumie (wiki.postgresql.org/wiki/Number_Of_Database_Connections, dostęp 24 sierpnia 2026).
idealna aktywna współbieżność = (rdzenie fizyczne × 2) + wydajne dyski. Liczba rdzeni wyklucza hyperthreading. W nowoczesnych pamięciach SSD liczba efektywnych dysków pozostaje bliska 1, gdzie pojęcie oddzielnego dysku fizycznego („wrzeciona”) traci wiele ze swojego pierwotnego znaczenia.
Na serwerze z 8 rdzeniami fizycznymi i pamięcią SSD wzór daje (8 × 2) + 1 = 17 aktywnych połączeń, zanim przepustowość zacznie spadać. Liczba ta często zaskakuje: wydaje się niewielka w porównaniu z setkami połączeń otwieranych przez aplikację w praktyce. Jest to właśnie tematem następnego akapitu.
Liczba obliczona za pomocą wzoru mierzy współbieżność, jaką może wchłonąć procesor i dysk, a nie liczbę połączeń klienckich, które aplikacja musi otworzyć. Flota 20 procesów aplikacyjnych, każdy z własną pulą 10 połączeń, otwiera 200 jednoczesnych połączeń z Postgres, nawet jeśli tylko 17 z nich aktywnie pracuje w danym momencie. Bez modułu pulowego max_connections musi obejmować 200, a nie 17. To właśnie ta luka popycha większość architektur do dodania modułu w trybie transakcyjnym, nawet jeśli oznacza to wybranie którego z nich (zobacz nasze porównanie PgBouncer, Supavisor i PgCat).
Jak zmienić max_connections (i dlaczego wymagane jest ponowne uruchomienie)
max_connections nie podlega wymianie podczas pracy. To jest parametr kontekstu postmaster: Postgres czyta go raz przy uruchomieniu, aby określić rozmiar pamięci współdzielonej. Ponowne załadowanie konfiguracji (pg_reload_conf() lub SIGHUP) nie wystarczy, należy zrestartować serwer.
Najpierw sprawdź bieżącą wartość i jej kontekst, aby potwierdzić, że konieczne będzie ponowne uruchomienie:
Następnie zastosuj nową wartość i uruchom ponownie:
max_connections zawiera domyślnie superuser_reserved_connections (domyślnie 3): te połączenia są zarezerwowane dla superużytkownika w przypadku nasycenia, nigdy nie są dostępne dla Twojej aplikacji, nawet jeśli licznik globalny nie został jeszcze osiągnięty.
Jak Aurabase budżetuje max_connections w swoich klastrach Postgres
Rozmiar max_connections to nie tylko ćwiczenie teoretyczne. Oto jak Aurabase budżetuje je w zarządzanych klastrach Postgres:
Klastry dedykowane: jeden klaster Postgres na projekt
Na tym poziomie (zobacz nasze porównanie dedykowana i współdzielona baza) każdy projekt otrzymuje własny klaster CloudNativePG i własny budżet max_connections, dostosowany do rozmiaru instancji:
| bezpłatny (dedykowany) | max_połączenia 50 | 1 instancja · 500 m vCPU · 512Mi |
|---|---|---|
| profesjonalny (domyślny) | max_połączenia 200 | 2 instancje · 1 vCPU · 2Gi |
| zespół | max_połączenia 300 | 3 instancje · 2 vCPU · 3Gi |
| biznes | max_połączenia 400 | 3 instancje · 2 vCPU · 4Gi |
Klastry współdzielone: kilka projektów organizacji, wspólny budżet
Na tej drugiej ścieżce wszystkie projekty tej samej organizacji łączą się za pośrednictwem modułu puli CNPG (PgBouncer, tryb transaction) przed współdzielonym serwerem podstawowym:
| bezpłatny | max_połączenia 50 | max_client_conn 100 | max_user_connections 20 |
|---|---|---|---|
| zawodowiec | max_połączenia 100 | max_client_conn 200 | max_user_connections 60 |
| zespół | max_połączenia 200 | max_client_conn 400 | max_user_connections 150 |
Wszystkie projekty w organizacji łączą się za pośrednictwem udostępnionej roli aplikacji. Dlatego samo max_user_connections ogranicza całkowitą liczbę połączeń z serwerem, które ta rola może otworzyć w całym klastrze: jest to prawdziwe zabezpieczenie globalne dla klastra, a nie max_client_conn, które ogranicza jedynie połączenia klientów z samym modułem puli.
Jednak ten moduł puli obsługuje tylko ruch aplikacji SDK. Ze swojej strony PostgREST pozostaje bezpośrednio podłączony do usługi podstawowej (usługa-rw): łączenie w trybie transakcyjnym przerwałoby mechanizm przeładowywania schematu, który nasłuchuje dedykowanego kanału LISTEN o nazwie pgrst. Jego własne połączenia (2 na replikę na poziomie współdzielonym, 10 na replikę na poziomie dedykowanym) wliczają się zatem bezpośrednio do budżetu max_connections podstawowego, poza jakimkolwiek basenem, dokładnie tego rodzaju „zapomniane” połączenie, które musi obejmować krok 1 poniższej procedury.
Kod wyraźnie dokumentuje te wspólne budżety puli jako wartości początkowe, które należy skalibrować w rzeczywistych warunkach, poprzez pomiar pg_stat_activity pod obciążeniem, a nie jako stałe wartości z opublikowanego benchmarku. Jest to ta sama dyscyplina, którą opisano w naszej metodologii testów porównawczych : zmierz przed korektą, a nie zgaduj, a potem miej nadzieję. Klastry te działają na PostgreSQL 16, wybór udokumentowany w naszym porównaniu Postgres 16 vs 17 vs 18.
5-etapowa procedura określania rozmiaru max_connections bez tworzenia puli
Ta procedura nie jest zależna od żadnego konkretnego narzędzia: dotyczy dowolnego serwera Postgres, zarządzanego lub hostowanego samodzielnie.
- Zlicz rzeczywiste połączenia klientów. Liczba procesów aplikacji pomnożona przez wielkość ich wewnętrznej puli plus narzędzia administracyjne, replikacja i monitorowanie. To właśnie ta liczba, a nie formuła, określa minimalną wartość max_connections.
- Oblicz idealną współbieżność swojego sprzętu za pomocą wzoru z wiki PostgreSQL: (rdzenie fizyczne × 2) + wydajne dyski. Liczba ta wskazuje, ile z tych połączeń może faktycznie pracować równolegle bez pogarszania przepustowości.
- Ustaw max_connections powyżej rzeczywistej potrzeby kroku 1, z marginesem na
superuser_reserved_connectionsi wszelkie narzędzia administracyjne, które otwierają własne połączenia poza aplikacją. - Zastosuj zmianę za pomocą opcji ALTER SYSTEM SET, a następnie zrestartuj serwer. To jest parametr postmastera: proste przeładowanie nie wystarczy, jak opisano powyżej.
- Monitoruj pg_stat_activity w czasie. Jeśli liczba bezczynnych połączeń znacznie przekracza liczbę aktywnych połączeń, nie jest to problem max_connections: jest to sygnał, że potrzebny jest Pooler przed serwerem, a nie większa liczba.
Żądanie monitorowania z kroku 5, bezpośrednio przydatne:
Kiedy formuła już nie wystarcza: oznaki, że potrzebujesz basenowca
Trzy sygnały systematycznie powracają, gdy samo max_connections nie wystarcza, niezależnie od jego wartości.
- Błąd
FATAL: sorry, too many clients alreadypojawia się w czasie szczytowego obciążenia, natomiast większość połączeń wyświetlanych przezpg_stat_activityjest w stanie bezczynności. - Aplikacja działa w środowisku bezserwerowym lub z tymczasowymi procesami roboczymi (funkcje brzegowe, krótkie zadania), które otwierają i zamykają połączenia znacznie szybciej, niż został zaprojektowany w modelu przetwarzania na połączenie Postgres.
- Powyższa formuła i procedura zostały już zastosowane, a rzeczywiste zapotrzebowanie na połączenia klientów w dalszym ciągu przekracza to, co może przydzielić dostępna pamięć bez narażania work_mem lubshared_buffers.
W tych trzech przypadkach poprawną odpowiedzią jest prawie zawsze moduł basenowy umieszczony pomiędzy aplikacją a Postgres, a nie wyższy parametr max_connections. Nasze porównanie PgBouncer, Supavisor i PgCat szczegółowo opisuje trzy opcje, a nasz przewodnik po trybie transakcyjnym wyjaśnia najczęstszy kompromis po wprowadzeniu puli. Informacje na temat strojenia Postgres poza połączeniami można znaleźć w naszej produkcyjnej liście kontrolnej strojenia Postgres.