Kategoria: Automatyzacja i autonomia
„Po co pula połączeń, jeżeli baza stoi na tej samej maszynie co skrypty?” Zasadna wątpliwość: między moimi usługami a serwerem PostgreSQL stoi z tego powodu jeszcze jeden kontener.
Opisuję jedną instancję PgBouncera, tę, która obsługuje osiem baz dziedzinowych mojego systemu domowego. Wszystkie wartości poniżej pochodzą z odczytu z 2 listopada 2026: z pliku pgbouncer.ini, z pliku z listą kont, z definicji usługi w pliku docker compose i z dziennika kontenera. Jest to stan z tego dnia, nie pomiar. Dlaczego tych baz jest akurat osiem, wyjaśniłem osobno, w tekście o jednej wspólnej drodze dostępu do nich.
Co właściwie stoi między skryptem a bazą
Jeden adres w miejsce ośmiu
Pula połączeń to proces, który przyjmuje połączenia od klientów i utrzymuje własny, mniejszy zbiór połączeń do serwera bazy. Klient zgłasza się do niej tak samo jak do PostgreSQL i nie wie, że nie rozmawia z bazą.
U mnie pierwsza korzyść jest porządkowa, nie wydajnościowa: każda usługa ma w konfiguracji ten sam adres i ten sam port, a nie adres serwera bazy. Wydajności, jak pokazuje reszta tego odczytu, jeszcze nie ruszałem.
Jak jest uruchomiony
Gotowy obraz, port wystawiony tylko na pętlę zwrotną
PgBouncer chodzi u mnie z gotowego obrazu edoburu/pgbouncer:latest, a nie z własnej kompilacji. Nie było tu czego dokładać: całość mojej konfiguracji mieści się w dwóch plikach, które wchodzą do kontenera tylko do czytania.
Polityka restartu to unless-stopped. Port jest opublikowany jako 127.0.0.1:6432, czyli widoczny wyłącznie z samej maszyny, choć wewnątrz kontenera proces nasłuchuje na wszystkich interfejsach. O tym, kto może w ogóle spróbować się połączyć, decyduje więc linia w pliku compose, a nie konfiguracja samego PgBouncera.
Usługa ma w pliku zadeklarowane 0,5 rdzenia, 128 MB pamięci i kontrolę zdrowia opartą na pg_isready. Zadeklarowane, czyli zapisane w pliku. W tym samym pliku compose trafiłem już na limit, którego działający kontener nie miał, więc nie przyjmuję tego na wiarę; na uruchomionym kontenerze nie sprawdziłem.
W montowaniach jest jeszcze jedna rzecz, której nie umiem uzasadnić: do kontenera wchodzi gniazdo Dockera. Pula połączeń nie ma powodu, żeby je widzieć. Nie wiem, czy to pozostałość po skopiowaniu bloku usługi. Własny plik compose warto czytać linia po linii.
Tryb puli, czyli jedyna decyzja, którą trzeba tu podjąć
Tryb sesyjny, ustawiony wprost
W pliku stoi pool_mode = session, ustawione jawnie, a nie domyślnie. W trybie sesyjnym połączenie klienta trzyma jedno połączenie do serwera aż do rozłączenia, więc pula nie podaje go innemu klientowi w przerwie między zapytaniami.
Skutek u mnie jest taki, że liczba połączeń docierających do PostgreSQL zależy od tego, ile usług jednocześnie trzyma otwarte połączenie, a nie od tego, ile zapytań w tym czasie wykonują. Przy pętlach, które budzą się o określonej godzinie i rozłączają po pracy, takie ustawienie nie wymaga nadzoru.
Czego nie wiem: czy tryb transakcyjny dałby tu cokolwiek. Nie zmierzyłem ani liczby jednoczesnych połączeń, ani czasu bezczynności, więc nie mam na czym oprzeć takiej zmiany.
Piętnaście linii, czyli co jest ustawione, a co zostało domyślne
Dwie wartości rozmiaru puli z siedmiu i ani jednego limitu czasu
Najbardziej pouczającą liczbą w tym odczycie była długość pliku. Konfiguracja, przez którą przechodzi dostęp do ośmiu baz, ma piętnaście linii.
| Ustawienie | Wartość w odczycie z 2 listopada 2026 | Stan |
|---|---|---|
pool_mode |
session |
ustawione wprost |
max_client_conn |
1000 | ustawione wprost |
default_pool_size |
50 | ustawione wprost |
min_pool_size |
brak wpisu | domyślne |
reserve_pool_size |
brak wpisu | domyślne |
reserve_pool_timeout |
brak wpisu | domyślne |
max_db_connections |
brak wpisu | domyślne |
max_user_connections |
brak wpisu | domyślne |
limity czasu, czyli wpisy *_timeout |
ani jednego | domyślne |
sekcja [databases] |
jedna linia z gwiazdką | ustawione wprost |
auth_type |
md5 |
ustawione wprost |
| uwierzytelnianie | plik z listą kont, bez auth_query |
ustawione wprost |
Z siedmiu wartości opisujących rozmiar puli ustawione są dwie. Znaczą co innego i łatwo je pomylić: max_client_conn mówi, ilu klientów może się do puli zgłosić, a default_pool_size mówi, ile połączeń pula otworzy do bazy dla jednej pary baza i użytkownik. Pierwsza jest sufitem po stronie klientów, druga po stronie serwera i to ona faktycznie ogranicza.
Limitów czasu nie ma w tym pliku ani jednego. Cała sekcja [databases] to również jedna linia, z gwiazdką zamiast nazw: wszystkie osiem baz dziedzinowych i baza samego n8n rozwiązują się przez ten jeden wpis. Skutek trzeba nazwać wprost: pula nie decyduje, o którą bazę klient może poprosić, i tę decyzję podejmują uprawnienia w PostgreSQL, nie ten plik.
Uwierzytelnianie jest plikowe, oparte na liście kont, a nie na pytaniu zadawanym bazie, a auth_type to md5. Nie jest to dziś najmocniejsza metoda, jaką PostgreSQL oferuje, i z rzeczy widocznych w tym pliku jest to najsłabszy punkt.
Kto przez niego faktycznie chodzi, a kto nie
Osiem wpisów z adresem bazy i jeden, który omija pulę
W pliku docker compose jest osiem wpisów z adresem bazy. Sześć należy do usług, które wskazują na pulę: n8n, digital-twin-api, db-event-bridge, eda-orchestrator, db-backup i infra-sentinel. Siódmy należy do samej puli i wskazuje na serwer PostgreSQL, bo to jest jej zadanie.
Ósmy jest wyjątkiem. Usługa delab-n8n łączy się z serwerem bazy wprost, pomijając pulę. Moja własna zasada mówi, że nic nie łączy się z bazą samo, a tutaj jedna rzecz łączy się sama. Nie umiem powiedzieć, czy to świadome odstępstwo, czy wpis, który się wymknął. Dopóki tam stoi, moja zasada ma wyjątek.
Największym klientem tej puli jest n8n, bo liczbę jego workflow i przebiegów policzyłem osobno, pisząc o tym, co znaczy ponad 150 workflow.
Co się na tym zepsuło, a co naprawiło się samo
Cztery zerwane połączenia po restarcie serwera bazy
W dzienniku kontenera z dnia odczytu są cztery wpisy o zerwanym połączeniu do serwera, wszystkie skupione zaraz po restarcie PostgreSQL około godziny 10:24. Pula zestawiła połączenia ponownie sama, bez działania z mojej strony.
Podaję to ostrożnie, bo łatwo zrobić z tego dowód niezawodności, a dowodem nie jest: to jedno okno jednego dnia. Nie policzyłem ani czasu działania bez błędu, ani odsetka nieudanych połączeń. Ten fragment dziennika pokazuje tylko tyle, że restart bazy stojącej pod pulą kosztuje kilka zerwanych połączeń, a nie ręczną naprawę.
Połowa wartości została domyślna
Siedem rzeczy, których ten odczyt nie rozstrzyga.
- Nie jest to zalecenie. To ustawienia jednej instancji, w której połowa wartości została domyślna.
- Nie zmierzyłem wydajności. Nie wiem, ile połączeń przechodzi przez tę pulę jednocześnie ani jak długo stoją bezczynne, więc nie wiem, czy liczby w tabeli są dobrane dobrze.
- Jedna linia z gwiazdką nie zastępuje uprawnień. Pula nie ogranicza, o którą bazę klient poprosi; ta kontrola musi być w PostgreSQL.
- Nie podaję nazw kont ani haseł. Lista kont zostaje w tych dwóch plikach.
- Zadeklarowane 0,5 rdzenia i 128 MB to zapis w pliku. Dla innej usługi sprawdziłem działający kontener i limitów tam nie było; dla tej nie sprawdziłem.
- Wyjątek nadal jest wyjątkiem. Jedna usługa omija pulę i ten odczyt tego nie zmienia, tylko to ujawnia.
- Nie jest to miara oszczędności czasu. Nie mam jej dla żadnej ze swoich dziedzin i konfiguracja puli jej nie przybliża.
Którą warstwą mojego systemu jest w tym wszystkim sama baza, opisałem, pisząc o cyfrowym bliźniaku i o tym, dlaczego to nie jest agent AI.