konekšn púlTakéConnection pooling, Pool spojení, Databázový poolPokročilý
Definice
Connection pool je sada předem otevřených spojení k databázi nebo jiné službě, kterou aplikace sdílí mezi požadavky místo toho, aby pro každý požadavek navazovala nové spojení. Požadavek si spojení vypůjčí, provede dotaz a vrátí ho zpět do fondu. Šetří se tím čas na TCP handshake, autentizaci a vznik procesu na straně serveru.
Proč je otevření spojení drahé
Navázání spojení k databázi není jen jeden síťový paket. Proběhne TCP handshake, případně TLS handshake, autentizace uživatele a na straně serveru vznikne obslužná struktura: u PostgreSQLu samostatný proces, u MySQL vlákno. Dohromady to trvá jednotky až desítky milisekund, což u dotazu trvajícího dvě milisekundy znamená, že režie mnohonásobně převáží vlastní práci.
Connection pool tuhle režii zaplatí jednou při startu nebo postupně podle zátěže a pak už jen půjčuje. Kód si vyžádá spojení, dostane ho z fondu, po dokončení práce ho vrátí. Z pohledu aplikace to vypadá stejně jako otevírání nového spojení, jen bez čekání.
Co pool ve skutečnosti chrání
Pool se často prodává jako výkonnostní trik, ale jeho důležitější role je ochranná. Databáze zvládne jen omezený počet souběžných spojení, protože každé zabírá paměť a plánovací kapacitu. Bez poolu může nárazová zátěž otevřít tisíce spojení, server se utopí v přepínání kontextu a spadne celý, ne jen pomalé dotazy.
Pool tvrdě omezuje horní hranici. Když jsou všechna spojení obsazená, další požadavek čeká ve frontě, dokud se některé neuvolní, nebo po vypršení časového limitu selže. Vzniká tak řízené zpomalení místo kolapsu.
Klíčové parametry a jejich ladění
- Maximální velikost: počet spojení, který pool nikdy nepřekročí. Součet přes všechny instance aplikace musí zůstat pod limitem databáze.
- Minimální velikost / idle: kolik spojení se drží otevřených i v klidu, aby první požadavek po pauze nečekal.
- Acquire timeout: jak dlouho požadavek čeká na volné spojení, než dostane chybu.
- Max lifetime: po jaké době se spojení zahodí a nahradí novým. Brání hromadění stavu a přežívání spojení k databázi, která už byla přepnuta na jinou repliku.
Větší pool není automaticky rychlejší. Pokud má databáze osm jader, sto souběžných dotazů se stejně vykonává postupně, jen s vyšší režií. Praktické maximum bývá řádově jednotky až nižší desítky spojení na instanci.
Kde pooling naráží
Serverless a autoscaling prostředí boří základní předpoklad poolu, tedy dlouho žijící proces. Každá instance funkce si drží vlastní fond a při špičce jich mohou běžet stovky. Řešením je externí pooler mezi aplikací a databází (PgBouncer, RDS Proxy, Prisma Accelerate) nebo přístup přes HTTP API. Souvislosti popisuje heslo Serverless.
Druhá past je stav na spojení. Dočasné tabulky, nastavené session proměnné, prepared statements nebo nedokončená transakce zůstanou na spojení i po jeho vrácení do fondu a další požadavek je zdědí. V transakčním režimu pooleru navíc nelze spolehlivě používat věci vázané na session, což bývá zdrojem nečekaných chyb.
Příklady z praxe
Node.js aplikace proti PostgreSQL
Webová aplikace v Node.js obsluhuje stovky požadavků za sekundu, ale databáze má limit 100 spojení. Pool s maximem 10 na instanci a acquire timeoutem 5 sekund udrží deset běžících kontejnerů bezpečně pod limitem. Při špičce požadavky krátce čekají ve frontě místo toho, aby databázi zahltily.
import { Pool } from 'pg'; const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 10, idleTimeoutMillis: 30_000, connectionTimeoutMillis: 5_000, }); // spojení se automaticky vrátí do fondu const { rows } = await pool.query('SELECT id FROM orders WHERE status = $1', ['new']);Zapomenuté vrácení spojení vyčerpá fond
Vývojář si vypůjčí spojení kvůli transakci, ale při výjimce chybí uvolnění ve finally. Pool se během několika minut provozu vyprázdní a aplikace začne padat na timeoutech, přestože databáze je nevytížená. Typický příznak je stoupající počet čekajících požadavků při nulovém zatížení serveru.
const client = await pool.connect(); try { await client.query('BEGIN'); await client.query('UPDATE accounts SET balance = balance - 100 WHERE id = $1', [id]); await client.query('COMMIT'); } catch (e) { await client.query('ROLLBACK'); throw e; } finally { client.release(); // bez tohoto řádku pool postupně vyschne }
Časté omyly
- MýtusČím větší pool nastavím, tím víc požadavků aplikace zvládne.
- Ve skutečnostiPropustnost limituje počet jader a diskové I/O databáze, ne počet spojení. Nad určitou hranicí přidané spojení jen zvyšuje soupeření o zdroje a prodlužuje latenci všem. Menší pool s frontou obvykle dává lepší a stabilnější časy odezvy.
- MýtusConnection pool řeší i to, že se databáze restartuje nebo přepne na repliku.
- Ve skutečnostiPool může držet spojení, která už na druhé straně neexistují nebo míří na starý uzel. Proto se nastavuje maximální životnost spojení a validace před zapůjčením; bez toho aplikace po failoveru vrací chyby, dokud se fond sám neobmění.
- MýtusServerless funkce pool nepotřebuje, protože běží jen chvíli.
- Ve skutečnostiServerless prostředí naopak pooling potřebuje nejvíc, jen ne uvnitř funkce. Každá souběžná instance si otevírá vlastní spojení, takže se limit databáze vyčerpá velmi rychle. Řešením je externí pooler nebo přístup přes HTTP datové API.
Časté dotazy
- Jak velký connection pool nastavit?
- Velikost connection poolu se odvozuje od kapacity databáze, ne od počtu uživatelů. Vyjděte z počtu jader databázového serveru a povahy zátěže: u dotazů vázaných na CPU stačí řádově jednotky spojení na instanci, u zátěže čekající na disk nebo síť o něco víc. Klíčové je, aby součet maxim přes všechny instance aplikace, cron úlohy, migrace a analytické nástroje zůstal bezpečně pod limitem databáze. Konečné číslo pak ověřte zátěžovým testem a sledujte dobu čekání na zapůjčení spojení.
- Co znamená chyba pool timeout nebo too many connections?
- Pool timeout znamená, že požadavek čekal déle, než dovoluje acquire timeout, a žádné spojení se neuvolnilo. Nejčastější příčiny jsou tři: nevrácená spojení kvůli chybějícímu release, příliš pomalé dotazy držící spojení dlouho, nebo prostě víc souběžných požadavků než míst ve fondu. Chyba too many connections přichází naopak přímo z databáze a znamená, že součet spojení všech klientů překročil server limit. První krok je vždy změřit, kolik spojení jednotlivé instance skutečně drží.
- Kdy nasadit externí pooler místo poolu v aplikaci?
- Externí pooler jako PgBouncer nebo RDS Proxy dává smysl ve chvíli, kdy k jedné databázi přistupuje mnoho krátce žijících procesů: serverless funkce, kontejnery s agresivním autoscalingem, více mikroslužeb nebo dávkové úlohy. V takové situaci nelze součet aplikačních poolů rozumně ohraničit. Pooler drží vůči databázi malý počet fyzických spojení a klientům nabízí mnohem větší počet logických. Daní je omezení v transakčním režimu, kde nefungují prepared statements vázané na session, dočasné tabulky ani advisory zámky.
- Potřebuje connection pool i aplikace s HTTP API nebo Redisem?
- Connection pooling se nepoužívá jen u relačních databází. Stejný princip aplikují HTTP klienti s keep-alive spojeními, klienti Redisu, message brokerů i gRPC kanály. Všude jde o totéž: vyhnout se opakovanému handshaku a omezit počet souběžných spojení proti službě. Rozdíl je v ceně jednoho spojení. Redis nebo HTTP server unese tisíce spojení mnohem snáz než PostgreSQL, takže tam bývají limity volnější, ale úplně bez horní hranice by se i tak dalo protistranu vyčerpat.
Zdroje
- PostgreSQL Documentation: Server Configuration(otevře se v novém okně)
- Amazon RDS Proxy(otevře se v novém okně)
- Cloud SQL documentation: Manage database connections(otevře se v novém okně)
- Redis documentation(otevře se v novém okně)