Dieser Gewinn hat einen bestimmten Preis: Der Transaktionsmodus unterbricht stillschweigend alles, was eine stabile Postgres-Verbindung von einer Anfrage zur nächsten voraussetzt. Sitzung SET, LISTEN/NOTIFY, Beratungssperren, Cursor, die die Transaktion überleben, benannte vorbereitete Anweisungen. Dieser Artikel beschreibt den Mechanismus detailliert, listet diese Grenzwerte mit ihren genauen Symptomen auf und zeigt dann, wie ein Backend in der Produktion (unseres, direkt in seinem Repository überprüft) ihn konfiguriert, ohne von ihm gefangen zu werden. Informationen zur Messmethode hinter den hier genannten Leistungszahlen finden Sie in unserer Benchmark-Methodik .
Das Wesentliche
- Transaktionsmodus: Die Serververbindung wird am Ende jeder Transaktion freigegeben, nicht wenn der Client die Verbindung trennt. Dies ist der effizienteste Modus zum Teilen kurzer Verbindungen vom Typ REST API.
- Aufgrund der Konstruktion nicht kompatibel mit: Sitzung SET/RESET, LISTEN/NOTIFY, Sitzungshinweissperren, WITH HOLD-Cursor, temporäre Tabellen, die von einer Anforderung zur anderen wiederverwendet werden.
- Die häufigste Falle in der Praxis: benannte vorbereitete Anweisungen, die mehrere Treiber (sqlx, asyncpg, der JDBC-pgjdbc-Treiber) standardmäßig aktivieren, können auf einer anderen Serververbindung abgespielt werden und unter Last einen Fehler wie
prepared statement does not existauslösen. - Seit Version 1.21 kann PgBouncer Protokoll-vorbereiteten Anweisungen im Transaktionsmodus folgen (LRU-Cache über Serververbindung). Dies verhindert nicht, dass der clientseitige Cache deaktiviert wird, wenn Ihre Anwendung
search_pathbei jeder Anfrage ändert. - Im Aurabase-Code verifiziert: Die Mandantenpools laufen mit
statement_cache_capacity(0)und PgBouncer inpool_mode=transaction, während PostgREST freiwillig in direkter Verbindung bleibt, um sein Schema über LISTEN/NOTIFY neu zu laden.
Die 3 Pooling-Modi von PgBouncer
PgBouncer bietet drei Modi, die sich nur darin unterscheiden, wann die Postgres-Serververbindung zum gemeinsamen Pool zurückkehrt. Die offizielle Dokumentation nennt sie session, transaction und statement (pgbouncer.org/features.html, Abschnitt „Pooling-Modi“, abgerufen am 24. August 2026).
| Mode | Serververbindung locker | Sitzungskompatibilität |
|---|---|---|
| Sitzung (Standard) | Beim Trennen der Client-Verbindung | Insgesamt: SET, LISTEN, Cursors, alles funktioniert wie live |
| Transaktion | Am Ende jeder Transaktion (COMMIT/ROLLBACK) | Teilweise: nur das, was für die Transaktion lokal bleibt |
| Aussage | Nach jeder individuellen Anfrage | Minimal: explizite Transaktionen mit mehreren Abfragen sind verboten |
Der Sitzungsmodus ist am freizügigsten, bietet jedoch die geringste Skalierbarkeit: Eine Postgres-Verbindung bleibt für einen Client reserviert, solange er verbunden bleibt, auch wenn zwischen zwei Anforderungen nichts unternommen wird. Der Anweisungsmodus ist für ganz bestimmte Fälle reserviert (Nur-Lese-Proxy, Gesundheitsprüfungen) und unterbricht sogar klassische explizite Transaktionen. Der Transaktionsmodus ist der in der Praxis vorherrschende Kompromiss für eine REST-API: Jede HTTP-Anfrage entspricht im Allgemeinen einer einzelnen kurzen Postgres-Transaktion.
So funktioniert der Transaktionsmodus, Verbindung für Verbindung
Im Transaktionsmodus stellt PgBouncer nur dann eine Serververbindung zu einem Client her, wenn dieser eine Transaktion öffnet, und gibt sie bei COMMIT oder ROLLBACK an den Pool zurück. Zwischen zwei Transaktionen kann es vorkommen, dass derselbe Client einer völlig anderen Serververbindung zugewiesen wird.
Konkret kann PgBouncer mit einem default_pool_size von 20 mehrere hundert gleichzeitige Kunden aufnehmen, bei denen zu einem bestimmten Zeitpunkt nur wenige Transaktionen tatsächlich ausgeführt werden. Es ist dieses Verhältnis, das den Transaktionsmodus für eine REST-API mit hohem Datenverkehr, aber kurzen Transaktionen rechtfertigt: Die seltene Ressource (eine Postgres-Verbindung, teuer im Speicher auf der Serverseite) wird nur für die unbedingt erforderliche Zeit belegt.
Dieser letzte Kommentar fasst das Wesentliche zusammen: Der Transaktionsmodus funktioniert, weil er absichtlich die Verbindung zwischen „meiner Anwendungssitzung“ und „meiner Postgres-Verbindung“ unterbricht. Alles, was auf diesem Link basiert, ist kaputt. Im nächsten Abschnitt wird genau aufgeführt, was.
Was bricht im Transaktions-Pooling-Modus zusammen?
In der offiziellen PgBouncer-Dokumentation werden PostgreSQL-Funktionen explizit aufgeführt, die ihre Bedeutung verlieren, sobald eine Serververbindung zwischen zwei Anfragen desselben Clients wiederverwendet werden kann.
| Betroffene Funktionalität | Warum geht es kaputt? | Typisches Symptom |
|---|---|---|
| SITZUNG EINSTELLEN / EINSTELLEN | Die Einstellung gilt für eine Verbindung, die unmittelbar danach wiederverwendet werden kann | Ein Parameter scheint zwischen zwei Anfragen zufällig vergessen worden zu sein |
| ZUHÖREN/BENACHRICHTIGEN | Setzt eine dauerhafte Verbindung voraus, um Benachrichtigungen zu empfangen | Der Kunde wird nie oder nur zeitweise benachrichtigt |
| Sitzungshinweissperren | Die Sperre wird von der Serververbindung gehalten, nicht vom logischen Client | Eine Sperre wird vor dem erwarteten Abschluss freigegeben oder wird nie freigegeben |
| MIT HOLD-Schiebereglern | Muss über die Transaktion hinaus, die es eröffnet hat, überleben | Fehler „Cursor existiert nicht“ bei der nächsten Iteration |
| Temporäre Tabellen | Bezieht sich auf die Postgres-Sitzung, nicht auf die Transaktion | Die Tabelle „verschwindet“ bei der nächsten Abfrage |
| Vorbereitete Aussagen benannt | Auf einer bestimmten Serververbindung vorbereitet, auf einer anderen wiedergegeben | „Vorbereitete Anweisung ... existiert nicht“ unter Last |
Die meisten dieser Einschränkungen machen sich in der lokalen Entwicklung nicht bemerkbar, wo typischerweise eine einzige Verbindung den gesamten Datenverkehr bedient. Sie treten unter realer Last auf, wenn tatsächlich mehrere Clients den Pool teilen und eine Serververbindung zwischen zwei Anfragen desselben logischen Clients tatsächlich den Besitzer wechselt. Ein Rauchtest bringt sie fast nie ans Licht.
Vorbereitete Aussagen: die am meisten missverstandene Grenze
Die meisten modernen Postgres-Treiber bereiten benannte Anfragen standardmäßig auf der Protokollseite vor, ohne dass der Anwendungscode dies explizit anfordert. Genau das macht es schwierig, diese Falle vorherzusehen.
Eine vom Protokoll vorbereitete Anweisung wird zum Zeitpunkt von Parsebenannt und auf einer bestimmten Serververbindung zwischengespeichert. Im Transaktionsmodus kann diese Verbindung zwischen zwei Anfragen desselben logischen Clients einem anderen Client zugewiesen werden. Wenn der Treiber dann denselben Anweisungsnamen auf einer Verbindung wiedergibt, für die er nie vorbereitet wurde, antwortet Postgres mit einem expliziten Fehler, normalerweise prepared statement "sqlx_s_N" does not exist für einen SQLX-Client. Das Verhalten ist sporadisch: Es hängt von der Leistung der Verbindungen unter Last ab und nicht von einem deterministischen Fehler, der bei jedem Aufruf reproduzierbar ist.
Die clientseitige Korrektur ist unabhängig von der Sprache dieselbe: Deaktivieren Sie den Cache benannter vorbereiteter Anweisungen oder erzwingen Sie unbenannte Abfragen für jeden Pool, der einen Pooler im Transaktionsmodus kreuzt. In Rust mit sqlx geht es bei den Verbindungsoptionen über statement_cache_capacity(0).
Seit Version 1.21 entschärft PgBouncer einen Teil des Problems auf der Serverseite: Es kann protokollvorbereitete Anweisungen im Transaktionsmodus verfolgen und sie im laufenden Betrieb auf der zugewiesenen Verbindung vorbereiten, wobei pro Verbindung ein LRU-Cache vorhanden ist, dessen Größe über max_prepared_statementsangepasst wird. Dies verringert die Anzahl der Fehler, befreit Sie jedoch nicht davon, den Client-Cache in einem Pool mit mehreren Mandanten zu deaktivieren, in dem sich search_path von einer Anforderung zur anderen ändert: Ein zwischengespeicherter Plan friert die interne Kennung (OID) der aufgelösten Tabelle zum Zeitpunkt von Parseein, und die Wiedergabe unter einem anderen Schema gibt möglicherweise Daten vom falschen Mandanten zurück und nicht einen einfachen Fehler.
So konfiguriert Aurabase PgBouncer im Transaktionsmodus
Das Aurabase-Repository stellt PgBouncer als pool_mode=transaction vor der gemeinsamen Datenebene (deploy/helm/aurabase/templates/infra/pgbouncer.yaml) und ein identisch konfiguriertes Pooler CNPG vor jeder dedizierten Postgres-Instanz eines Mandanten (deploy/cnpg/tenant-pooler.yaml) bereit. Beide Wege wenden die gleiche oben beschriebene Disziplin an.
Der Quellcode dokumentiert einen spezifischen Sicherheitsgrund für diese Wahl, nicht nur einen Stabilitätsgrund. Von Mietern gemeinsam genutzte Postgres-Pools positionieren bei wiederverwendeten Verbindungen einen anderen search_path pro Projekt. Eine zwischengespeicherte vorbereitete Anweisung friert die OID der aufgelösten Tabelle zum Zeitpunkt von Parseein; Die Wiedergabe für einen anderen Mandanten auf derselben Verbindung würde die Abfrage anhand des Schemas des ersten Mandanten ausführen, was eine Umgehung der Isolation und nicht nur einen Anwendungsfehler darstellt. statement_cache_capacity(0) wird daher ausnahmslos angewendet, auch auf dedizierten Instanzen, die auch im Transaktionsmodus einen CNPG-Pooler durchlaufen.
Zweite Sitzungshygienemaßnahme: Wenn jede Verbindung zum Pool zurückkehrt, führt ein Hook DISCARD ALL aus (Einstellungen zurücksetzen, vorbereitete Anweisungen auf der Serverseite freigeben, Beratungssperren freigeben, Cursor und temporäre Tabellen löschen). Ohne diesen Hook könnte ein Sitzungsrest, der von einer vorherigen Anfrage stammt, bei der nächsten Anfrage eines anderen Mandanten verloren gehen, der dieselbe recycelte Verbindung wiederverwendet.
Ausnahme akzeptiert: Dedizierte PostgREST-Instanzen bleiben in direkter Verbindung mit der Primärinstanz, ohne den Pooler zu durchlaufen. Das Neuladen des PostgREST-Schemas basiert auf LISTEN/NOTIFY, das eine dauerhafte Verbindung voraussetzt, genau die Funktionalität, die der Transaktionsmodus unterbricht (Details wurden bereits in unserem Artikel über PostgREST-Kompatibilität bei Aurabasedokumentiert). RLS-Einstellungen pro Anfrage werden innerhalb einer expliziten Transaktion an SET LOCAL übergeben. Dies ist die einzige Möglichkeit, mit einem Pool kompatibel zu bleiben, der Serververbindungen bei jedem COMMIT ändern kann (siehe unseren Artikel übermehrinstanzenfähige RLS-Isolation).
Aktivieren Sie den Transaktionsmodus, ohne Ihre Anwendung zu beschädigen
Eine kurze Checkliste, die für jedes Backend gilt, das von einer direkten Postgres-Verbindung zu einem PgBouncer im Transaktionsmodus wechselt.
- Überprüfen Sie den Anwendungscode. Suchen Sie nach Nicht-Transaktionssperren
SET,LISTEN/NOTIFY, Sitzungshinweissperren,WITH HOLDCursorn und temporären Tabellen, die zwischen Abfragen wiederverwendet werden. - Ersetzen Sie Sitzungs-SETs durch LOKALE-SETs innerhalb einer expliziten Transaktion. Dies ist die einzige Einstellung, die das Verbindungsrecycling ordnungsgemäß übersteht, da sie bei COMMIT/ROLLBACK bereinigt wird und nicht bei der nächsten Verbindung verloren geht.
- Deaktivieren Sie den treiberseitigen Cache für vorbereitete Anweisungen, wenn Ihr Pool den Pooler durchläuft und sich das Schema oder die Rolle von einer Anforderung zur anderen ändert. Die Leistungskosten sind real, aber messbar und viel geringer als das Risiko von Verlusten zwischen Mietern.
- Isolieren Sie Verbindungen, die wirklich den Sitzungsmodus benötigen (Migrationen, Admin-Skripte, alles, was von LISTEN/NOTIFY abhängt), auf eine direkte Nicht-Pooler-Verbindung, anstatt auf den Transaktionsmodus für den gesamten anderen Datenverkehr zu verzichten.
- Größe
default_pool_sizeundmax_client_connrelativ zum tatsächlichen Postgresmax_connections, nicht durch eine beliebige Zahl, die aus einem anderen Projekt kopiert wurde. - Test unter realer Last, nicht nur Rauchtest. Vorbereitete Anweisungsfehler und Sitzungseinstellungslecks treten fast nie bei einer einzelnen lokalen Verbindung auf.
- Überwachen Sie
SHOW POOLSundSHOW STATSvon der PgBouncer-Verwaltungskonsole aus, sobald sie in der Produktion sind, um die Poolsättigung zu erkennen, bevor sie auf der Clientseite sichtbar wird.
Sollten Sie immer den Transaktionsmodus statt der Sitzung wählen?
Nein, aber es ist die richtige Standardauswahl für die überwiegende Mehrheit der REST-APIs. Der Sitzungsmodus ist nach wie vor vorzuziehen für eine Legacy-Anwendung, die stark von Sitzungsfunktionen abhängt, die Sie nicht schnell umgestalten können, oder für geringen Datenverkehr, bei dem der Pooling-Gewinn den Migrationsaufwand nicht ausgleicht.
PgBouncer ist auch nicht die einzige Implementierung dieses Pooling-Modells: Supavisor (Supabase) und PgCat sind zwei aktuelle Alternativen mit unterschiedlichen Kompromissen bei Lastverteilung und Clustering. Sehen Sie sich unseren detaillierten Vergleich an, PgBouncer vs Supavisor vs PgCat, um je nach Topologie zwischen den dreien zu wählen.
FAQs
Die Fragen, die am häufigsten auftauchen, wenn der Transaktionsmodus in der Produktion aktiviert wird.