Connection-Pool der PostgreSQL-Erweiterung
Der PostgreSQL-Server startet für jede eingehende Client-Verbindung einen Backend-Prozess. Dieses Modell führt bei der postgres-Erweiterung zu den folgenden zwei Punkten, die Leistungsprobleme verursachen können:
- Das Öffnen einer neuen Verbindung ist vergleichsweise teuer
- Die Anzahl der gleichzeitig geöffneten Verbindungen sollte nicht zu hoch sein
Um diese Punkte zu adressieren, verwendet die Erweiterung einen In-Memory-Connection-Pool. Damit bleibt eine begrenzte Anzahl von Verbindungen nach der Verwendung geöffnet (und idle) und kann für nachfolgende Abfragen wiederverwendet werden.
Parallele Scans
Aufgrund der multithreaded Abfrageverarbeitung von DuckDB kann die postgres-Erweiterung mehrere Verbindungen öffnen,
auch für Abfragen, die einen Scan einer einzelnen PostgreSQL-Tabelle ausführen. Die Anzahl paralleler Abfragen für den Scan einer einzelnen Tabelle kann so hoch sein wie die Anzahl der Worker-Threads der aktuellen DB-Instanz (globale Konfigurationsoption threads).
Wenn der Parameter threads zu hoch gesetzt ist (was für die lokale Verarbeitung anderer Abfragen auf DuckDB-Seite nötig sein kann), kann das dazu führen, dass versucht wird, eine übermäßige Anzahl von PostgreSQL-Verbindungen zu öffnen. Um das zu verhindern, werden diese parallelen Abfragen (für denselben Tabellen-Scan) zusätzlich durch die maximale Anzahl erlaubter Verbindungen im Connection-Pool begrenzt (standardmäßig höchstens 32).
Auch wenn der Connection-Pool so konfiguriert ist, dass bei belegten Verbindungsslots zusätzliche Verbindungen geöffnet werden (force-Acquire-Modus), verwenden die zusätzlichen parallelen Abfragen immer den Modus try. Ist für sie keine Verbindung verfügbar (die maximale Anzahl der Verbindungen im Pool ist überschritten), nimmt dieser Worker-Thread nicht weiter an der Verarbeitung dieses Tabellen-Scans teil; der Scan läuft mit den Worker-Threads weiter, die eine Verbindung aus dem Pool erhalten konnten.
Siehe die Beschreibung der Funktion postgres_configure_pool für Details.
Verbindungsproxies
Ein Connection-Pool ist am wirksamsten, wenn DuckDB sich direkt mit einem PostgreSQL-Server verbindet und für jede neue Verbindung ein neuer Backend-Prozess gestartet wird. Wird zwischen DuckDB und PostgreSQL ein transparenter Zwischenproxy verwendet (zum Beispiel PgBouncer), führt das Öffnen einer neuen Verbindung nur zum Öffnen eines neuen Netzwerk-Sockets, nicht zu einem vollständigen Backend-Prozess.
In Deployment-Umgebungen mit Verbindungsproxies kann es daher sinnvoller sein, den Idle-Timeout auf einen niedrigeren Wert zu setzen (über die Konfigurationsoption pg_pool_idle_timeout_millis oder die Funktion postgres_configure_pool) oder das Pooling vollständig zu deaktivieren (durch Setzen von pg_pool_max_connections = 0).
Reaper-Thread
Die postgres-Erweiterung unterstützt einen Hintergrund-Thread (sogenannter „Reaper-Thread“), der periodisch idle Verbindungen im Pool prüft und diejenigen schließt, die die Werte idle_timeout_millis oder max_lifetime_millis überschreiten.
Für jeden Connection-Pool wird ein eigener Thread gestartet; er kann über die Konfigurationsoption pg_pool_enable_reaper_thread aktiviert bzw. deaktiviert werden.
Wenn der Reaper-Thread nicht läuft, können die Werte idle_timeout_millis oder max_lifetime_millis der Verbindungen trotzdem geprüft werden, allerdings nur, wenn eine Verbindung aus dem Pool entnommen oder in den Pool zurückgegeben wird.
Thread-lokaler Cache
Idle Verbindungen in einem Pool stehen jedem aufrufenden Thread zur Verfügung, sei es ein interner Worker-Thread von DuckDB oder ein neuer Client-Thread. In manchen Fällen kann es vorteilhaft sein sicherzustellen, dass nachfolgende Abfragen desselben Threads auf derselben Verbindung wie die erste Abfrage laufen.
Dazu kann die Konfigurationsoption pg_pool_enable_thread_local_cache verwendet werden – sie sorgt dafür, dass eine idle Verbindung in einen thread-lokalen (und thread-privaten) Cache zurückgegeben wird statt in den von allen Threads geteilten Hauptcache.
Warning Thread-lokale Verbindungen werden vom Reaper-Thread weder geprüft noch aufgeräumt. Der thread-lokale Cache sollte mit Vorsicht verwendet werden, da gecachte Verbindungen, obwohl sie für andere Threads nicht verfügbar sind, weiterhin einen Platz im Pool belegen und so eine „Pool-Starvation“ verursachen können.
Konfigurationsoptionen
Die folgenden globalen Konfigurationsoptionen können zur Konfiguration des Connection-Pools verwendet werden. Diese Optionen gelten nur für Datenbanken, die nach dem Setzen der Option angehängt werden. Um die Einstellungen eines Connection-Pools für eine bereits angehängte Datenbank zu ändern, kann stattdessen die Funktion postgres_configure_pool verwendet werden.
pg_pool_acquire_mode(VARCHAR, Standard: ‘force’): wie Verbindungen aus dem Pool bezogen werden: ‘force’ (immer verbinden, Pool-Limit ignorieren), ‘wait’ (blockieren, bis eine verfügbar ist), ‘try’ (sofort fehlschlagen, wenn keine verfügbar ist)pg_pool_max_connections(UBIGINT, Standard:4 <= cpu_count * 1.5 <= 32): maximale Anzahl der Verbindungen, die in einem Connection-Pool für jede angehängte Postgres-Datenbank zwischengespeichert werden dürfen. Diese Zahl kann bei parallelen Scans vorübergehend überschritten werden.pg_pool_wait_timeout_millis(UBIGINT, Standard:30000): maximale Wartezeit in Millisekunden beim Beziehen einer Verbindung aus einem Pool, in dem alle verfügbaren Verbindungen bereits belegt sind.pg_pool_enable_thread_local_cache(BOOLEAN, Standard:FALSE): ob das Zwischenspeichern von Verbindungen im thread-lokalen Cache aktiviert werden soll. Solche Verbindungen werden an die Threads gebunden und anderen Threads nicht zur Verfügung gestellt, belegen aber weiterhin einen Platz im Pool.pg_pool_max_lifetime_millis(UBIGINT, Standard:0nicht erzwungen): maximale Anzahl von Millisekunden, die die Verbindung offen gehalten werden kann. Dieser Wert wird geprüft, wenn die Verbindung aus dem Pool entnommen und in den Pool zurückgegeben wird. Wenn der Reaper-Thread des Connection-Pools aktiviert ist (Option ‘pg_pool_enable_reaper_thread’), wird dieser Wert periodisch im Hintergrund geprüft.pg_pool_idle_timeout_millis(UBIGINT, Standard:60000): maximale Anzahl von Millisekunden, die die Verbindung idle im Pool gehalten werden kann. Dieser Wert wird geprüft, wenn die Verbindung aus dem Pool entnommen wird. Wenn der Reaper-Thread des Connection-Pools aktiviert ist (Option ‘pg_pool_enable_reaper_thread’), wird dieser Wert periodisch im Hintergrund geprüft.pg_pool_enable_reaper_thread(Standard:TRUE): ob der Reaper-Thread des Connection-Pools aktiviert werden soll, der den Pool periodisch prüft, um ‘max_lifetime_millis’ und ‘idle_timeout_millis’ zu kontrollieren, und Verbindungen schließt, die die angegebenen Werte überschreiten. Entweder ‘max_lifetime_millis’ oder ‘idle_timeout_millis’ muss auf einen Wert ungleich null gesetzt sein, damit diese Option wirksam ist.pg_pool_health_check_query(VARCHAR, Standard:SELECT 1): die Abfrage, mit der geprüft wird, ob die Verbindung gesund ist. Das Setzen dieser Option auf eine leere Zeichenkette deaktiviert den Health-Check.