PostgreSQL ist in der neuen Hauptversion 16 seit September letzten Jahres verfügbar und mittlerweile bei 16.2 angelangt – der zweiten Runde an Fehlerbereinigungen. Wie gewohnt strotzt auch das aktuelle Release nur so vor neuen Funktionen und Erweiterungen bestehender Features. Unser Artikel fokussiert entsprechend auf einige der wichtigsten Änderungen, etwa bei der Logical Replication und beim I/O-Monitoring.
Für eine tiefergehende Recherche der umfangreichen Liste an Neuerungen seien bereits an dieser Stelle die erschöpfenden PostgreSQL Release Notes für Version 16 empfohlen [1]. Wir wollen im Folgenden nur auf größere Änderungen eingehen.
Neuerungen bei Logical Replication
Logical Replication steht seit Version 10 von PostgreSQL zur Verfügung und erfährt von Hauptversion zu Hauptversion stetig Verbesserungen und vor allem Erweiterungen der Funktionalität. Mit PostgreSQL 16 wird Logical Replication um die Fähigkeit der parallelen Transaktionsverarbeitung erweitert. Vor allem aufwendige und damit langlaufende Transaktionen profitieren hiervon. In PostgreSQL werden Replicas, die auf logischer Ebene replizieren, Subscriber genannt.
Eine solche Subscription kann nun mehrere sogenannte Apply Worker zum Anwenden der Änderungen von Transaktionen in die Tabellen verwenden. Der neue Konfigurationsparameter "max_parallel_apply_workers_per_subscription" steuert die Anzahl der parallelen Apply Worker pro Subscription. Aktivieren lässt sich die parallele Verarbeitung nur für sogenannte Streaming Subscriptions. Die Obergrenze an Workern, die von "max_ parallel_apply_workers_per_subscription" definiert werden, setzt dabei der Parameter "max_logical_replication_workers".
Für eine tiefergehende Recherche der umfangreichen Liste an Neuerungen seien bereits an dieser Stelle die erschöpfenden PostgreSQL Release Notes für Version 16 empfohlen [1]. Wir wollen im Folgenden nur auf größere Änderungen eingehen.
Neuerungen bei Logical Replication
Logical Replication steht seit Version 10 von PostgreSQL zur Verfügung und erfährt von Hauptversion zu Hauptversion stetig Verbesserungen und vor allem Erweiterungen der Funktionalität. Mit PostgreSQL 16 wird Logical Replication um die Fähigkeit der parallelen Transaktionsverarbeitung erweitert. Vor allem aufwendige und damit langlaufende Transaktionen profitieren hiervon. In PostgreSQL werden Replicas, die auf logischer Ebene replizieren, Subscriber genannt.
Eine solche Subscription kann nun mehrere sogenannte Apply Worker zum Anwenden der Änderungen von Transaktionen in die Tabellen verwenden. Der neue Konfigurationsparameter "max_parallel_apply_workers_per_subscription" steuert die Anzahl der parallelen Apply Worker pro Subscription. Aktivieren lässt sich die parallele Verarbeitung nur für sogenannte Streaming Subscriptions. Die Obergrenze an Workern, die von "max_ parallel_apply_workers_per_subscription" definiert werden, setzt dabei der Parameter "max_logical_replication_workers".
Logical Replication repliziert per Publication, eine Subscription registriert eine beliebige Zieldatenbank für die logische Replikation entsprechend der in der Publication enthaltenen Tabellen. Die neue Einstellung "parallel" der Option "streaming" aktiviert die parallele Verarbeitung einer Transaktion.
Vorteile ergeben sich vor allem dann, wenn Transaktionen sehr viele Änderungen an replizierten Tabellen vornehmen. Standardmäßig werden Transaktionen erst dann dekodiert und repliziert, wenn diese erfolgreich abgeschlossen sind: Langlaufende Transaktionen können so für enorme Verzögerungen bei der Verarbeitung der logischen Replikation sorgen. Die Option "streaming=on" überträgt Änderungen sofort, ohne den abschließenden Commit der Transaktion abzuwarten. Die Änderungen werden auf dem Subscriber zwischengespeichert und bei Commit letztlich in die Datenbasis eingespielt.
Das Abarbeiten von großen Transaktionen mit mehreren Apply Workern mittels "streaming=parallel" kann daher diesen Prozess stark beschleunigen. Der führende Apply-Worker-Prozess (Leader) übergibt per Replication-Stream erhaltene Änderungen sofort an einen Parallel Apply Worker, der diese in die Datenbasis schreibt. Steht kein Parallel Apply Worker zur Verfügung, werden die Änderungen doch auf die Platte geschrieben und die Parallel Apply Worker entsprechend signalisiert (Partial Serialization Mode).
Listing 1 zeigt ein vollständiges Beispiel, wie sich die neue parallelisierte Verarbeitung von Transaktionen mittels Logical Replication in PostgreSQL 16 konfigurieren lässt. Grundlage ist die Publication "pub_test" auf der Quelldatenbank beziehungsweise -instanz.
Bei der effizienten Replikation von logischen Änderungen auf einen Subscriber war es bisher unbedingt notwendig, entweder einen passenden Primary Key "REPLICA IDENTITY DEFAULT" oder per "REPLICA IDENTITY USING INDEX" einen entsprechenden kompatiblen Index definiert zu haben. Beide Identitäten haben aber den Nachteil, dass sie zwar die jeweiligen Änderungen replizieren, aber nicht die vollständige vorhergehende Version der Zeile enthalten, sondern nur die entsprechenden Schlüsselwerte und die neuen Werte. In diesen Fall kommt "REPLICA IDENTITY FULL" zum Einsatz.
Dies hat jedoch zur Folge, dass die Replikation auf dem Subscriber keine entsprechenden Indexe mehr verwendet hat. Das Auffinden der zu aktualisierenden Zeile mit den entsprechenden Schlüsselwerten führt unweigerlich für jede Aktualisierung zum Sequential Table Scan, dem vollständigen Absuchen der Zieltabelle. PostgreSQL 16 ermöglicht nun automatisch das Verwenden entsprechender Indexe auf dem Subscriber. Entsprechende Indexe müssen auf jeden Fall vom Typ B-Tree, nicht-partiell und mindestens eine reine Spaltenreferenz aufweisen – das schließt Indexe auf Ausdrücken et cetera automatisch aus.
Logische Replikation von Standby-Instanzen
Ebenfalls neu in PostgreSQL 16 ist die Funktion, logisches Dekodieren beziehungsweise das Replizieren von Standby-Servern aus zu ermöglichen. Bisher war dies nur von primären Instanzen aus möglich, was das Auslagern auf einen oder mehrere Standbys verhinderte. Hiermit sind nun sehr flexible Setups für logische Replikation möglich, ohne den primären Server weitergehend zu belasten. Dabei werden entsprechende Publikationen wie gewohnt auf dem primären Server angelegt. Der physische Standby bekommt die Publikationen repliziert.
Der Subscriber lässt sich mit seiner Subscription so konfigurieren, dass er anstatt die dekodierten logischen Änderungen vom primären Server nun von Standby erhalten kann. Allerdings gibt es hier noch eine kleine Einschränkung: Wird die Subscription angelegt, muss diese immer warten, bis alle laufenden Transaktionen ins Transaktionslog synchronisiert worden sind. Dies geschieht auf dem Standby nicht, sodass hier eine neue Funktion "pg_log_standby_snapshot()" auf dem primären Server aufzurufen ist. Geschieht dies nicht, scheint das "CRE-ATE SUBSCRIPTION"-Statement auf dem Subscriber zu hängen.
Das folgende Listing zeigt den Aufruf auf einem primären Server, um laufende Transaktionen ins Transaktionslog zu synchronisieren – dies erfordert Superuser-Berechtigungen:
test=# SELECT pg_log_standby_snapshot();
pg_log_standby_snapshot
-------------------------
0/60A9728
(1 row)
Das logische Replizieren von Standby-Instanzen ermöglicht auch das Auslagern und Skalieren moderner Infrastruktur mittels Streaming-Technologien. So können auch Change-Data-Capture-Werkzeuge wie das freie Projekt Debezium auf Standby-Server ausgelagert werden, damit lässt sich eine bessere Aufteilung entsprechender Aufgaben auf spezifische Knoten erreichen.
Verbessertes Monitoring von I/O
Mit der Einführung des neuen Views "pg_stat_io" erhält PostgreSQL ein sehr mächtiges Werkzeug zur Überwachung von I/O-Operationen einer Instanz. Bild 1 zeigt die Definition beziehungsweise Spalten des Views. Er erlaubt das Ermitteln und Überwachen von PostgreSQL-prozessbezogenen I/O-Informationen, beispielsweise über Autovacuum, von normalen Datenbankverbindungen oder PostgreSQL-eigenen internen Prozessen.
Bild 1: Der neue System-View "pg_stat_io" hilft bei der Überwachung von I/O-Operationen.
Die dabei zur Verfügung gestellten Metriken gliedern sich hierbei in unterschiedlichen Kontext, welche Prozesse (in PostgreSQL "backends" genannt) wie und welche I/O durchgeführt haben:
- backend_type: Der Typ des Datenbankprozesses, der die I/O Operationen durchgeführt hat. Clientbasierte, also normale Datenbankverbindungen von Applikation oder Nutzern sind als "client backend" klassifiziert, Autovacuum wird als "autovacuum worker" geführt. Zusätzlich gibt es noch eine Reihe anderer interner Backendtypen in PostgreSQL, deren I/O-Metriken sich ebenfalls ermitteln lassen. Die wichtigsten sind "startup" (Bei Crash-Recovery oder physischer Replikation auf dem Standby), "autovacuum launcher" (kontrolliert Autovacuum-Worker-Prozesse), "checkpoint" (PostgreSQL eigener Prozess, der interne Checkpoints verarbeitet) sowie "walsender" (für Replikation zuständiger PostgreSQL eigener Prozess).
- object: Zielobjekt der I/O-Operationen, entweder "relation" oder "temp relation".
- context: Diese Spalte kann als Wert entweder "normal", "vacuum", "bulkread" oder "bulkwrite" annehmen. Normal sind Blockoperationen, die etwa von einer Tabelle oder einem Index mittels Shared-Buffer-Pool lesen oder schreiben. Vacuum bezieht sich auf I/O-Operationen im Kontext von "VACUUM" oder "ANALYZE". Bulkread und bulkwrite klassifizieren I/O-Anfragen, die außerhalb des Shared-Buffer-Pools passieren, etwa durch sequenzielles Lesen einer großen Tabelle oder Copy-Operationen, also das Laden von Daten (hierfür werden dedizierte Ringbuffer verwendet).
Schnelleres und flexibleres VACUUM
"VACUUM" und "ANALYZE" lassen sich in PostgreSQL 16 hinsichtlich ihrer Nutzung des Shared-Buffer-Pools nun flexibel konfigurieren. Bisher nutzten beide Kommandos einen dedizierten Ringbuffer von 256 KByte im Shared-Buffer-Pool. Mit der neuen Kommandooption "BUFFER_USAGE_LIMIT" kann dieser jetzt einen konfigurierbaren Wert zwischen 128 KByte und 16 GByte annehmen. Die Syntax der neuen Option sieht so aus:
VACUUM (BUFFER_USAGE_LIMIT '1GB');
ANALYZE (BUFFER_USAGE_LIMIT '512MB');
Überschreitet der angegebene Wert ein Achtel der verfügbaren Größe des Shared-Buffer-Pools, wird der Wert entsprechend gekappt. Mittels des neuen Parameters "VACUUM_BUFFER_USAGE_ LIMIT" ist dies auch per Konfigurationsdatei global einstellbar. Der Wert lässt sich zur Laufzeit auch ändern. Der Standardwert orientiert sich an der bisherigen Größe von 256 KByte. Wenn für VACUUM und ANALYZE viel Arbeit ansteht, kann sich das Anpassen des Pools positiv auf die Verarbeitungsgeschwindigkeit der Kommandos auswirken. Gleichzeitig sorgt das automatische Begrenzen auf ein Achtel des verfügbaren Buffer-Pools dafür, den Cache nicht zu sehr zu strapazieren.
Wenn eine Zeile in PostgreSQL ein bestimmtes Alter erreicht hat, muss, um ein Überlaufen des Transaktionszählers zu verhindern, VACUUM ein sogenanntes Freezing der Zeile vornehmen. Hierbei wird der Transaktionszähler im Tupleheader der Zeile auf einen festgelegten, immer gültigen Wert gesetzt (FrozenXID). In PostgreSQL 16 kann dies nun statt auf Zeilenebene auch auf Blockebene passieren, was zu einer effizienteren Ausnutzung von Transaktionslog und Vorgang des Freezing führt.
Generische Pläne mit EXPLAIN
Prepared Statements in PostgreSQL werden auf der Basis unterschiedlicher Plantypen ausgeführt: Generic, also generische, und sogenannte custom, also maßgeschneiderte, Pläne. Dies spielt insbesondere bei parametrisierten Abfragen eine Rolle, da maßgeschneiderte Pläne die Eingabewerte der Parameter bei der Abfrageausführung berücksichtigen und daher in der Regel deutlich effizienter sind und sich damit die Abfrage schneller ausführen lässt. Generische Pläne haben aber den Vorteil, bei der Ausführung keinen zusätzlichen Aufwand zu erzeugen.
Daher geht PostgreSQL wie folgt vor: Bei den ersten fünf Ausführungen eines Prepared Statement wird dieses jeweils mit einem maßgeschneiderten Plan ausgeführt. Am Ende werden die durchschnittlichen Kosten dieser Pläne berechnet und ein generischer Plan erstellt. Ist dieser Plan von den Kosten vergleichbar, findet dieser für die zukünftigen Ausführungen der vorbereiteten Abfrage Verwendung.
Ein generischer Plan kann jedoch für bestimmte Ausführungen wieder ineffizient sein. Diesen generischen Plan als Administrator zu ermitteln, war bis PostgreSQL 16 relativ mühsam, die neue Option "GENERIC_PLAN" des EXPLAIN-Kommandos ermöglicht nun das einfache Ermitteln der generischen Pläne von parametrisierten Abfragen – ein Anwendungsbeispiel zeigt Bild 2.
Bild 2: Die neue EXPLAIN-Option "GENERIC_PLAN" zeigt den generischen Plan einer parametrisierten Abfrage an.
Neue JSON-Funktionen
PostgreSQL 16 führt die im SQL/JSON-Standard vorgegebenen JSON-Funktionen "JSON_ARRAY()", "JSON_ARRAYAGG()" sowie "JSON_OBJECT()" und "JSON_OBJECTAGG()" ein. Bild 3 verwendet beispielhaft "JSON_ARRAYAGG", um die Ergebnisse der Abfrage in ein JSON-Array zu aggregieren. Das Ergebnis ist hierbei wieder ein JSONB-Dokument.
Bild 3: "JSON_ARRAYAGG()" aggregiert die Titel von Star-Wars-Filmen in ein JSON-Array.
Mit dem neuen standardkonformen Ausdruck "IS JSON" lernt PostgreSQL des Weiteren das Testen von entsprechenden Datenwerten auf JSON-Kompatibilität. "IS JSON" und seine negierte Variante "IS NOT JSON" prüft Datenwerte (text, json, jsonb und bytea), ob deren Inhalte valide JSON-Objekte darstellen.
Der Operator testet dabei den linken Datenwert auf das gewünschte Ergebnisziel. Alternativ lässt sich mit dem Schlüsselwort "WITH UNIQUE KEYS" beziehungsweise dessen Negierung "WITHOUT UNIQUE KEYS" auf doppelte Schlüsselwerte innerhalb des JSON-Dokuments prüfen.
Für das spezifische Testen stehen die Schlüsselwörter "OBJECT" sowie "ARRAY" und "SCALAR" zur Verfügung. "IS JSON" ist besonders dann nützlich, wenn Datenwerte vor dem Konvertieren in finale json- oder jsonb-Datenwerte geprüft werden müssen, ohne dass die Gefahr von Transaktionsfehlern besteht. Mit diesen neuen Funktionen erweitert PostgreSQL seine standardkonforme Implementierung von SQL/JSON weiter.
LZ4 und Zstandard als neue Komprimierung
Um logische Sicherungen effizienter komprimieren zu können, unterstützt PostgreSQL 16 nun auch LZ4 oder Zstandard zusätzlich zu der bisherig vorhandenen Methode mittels zlib. Dies bedeutet wesentlich mehr Flexibilität hinsichtlich Geschwindigkeit und Effizienz. Besonders Zstandard ist hier hervorzuheben,das eine hervorragende Balance zwischen CPU-Nutzung und Komprimierungsrate bietet.
Verbesserungen am Berechtigungskonzept
Die neue Rolle "pg_create_subscription" erlaubt nun auch unprivilegierten Rollen – falls diesen Mitgliedschaft gewährt wurde –, Subscriptions anzulegen. Allerdings müssen diese zwingend mit der Option "password_required=true" spezifiziert sein. Ferner benötigt diese Rolle auch das CREATE-Privileg in der jeweiligen Datenbank.
Daneben gibt es nun auch eine vordefinierte Rolle "pg_use_reserved_connections". Rollen mit dieser Mitgliedschaft erhalten reservierte Verbindungen aus dem mit dem neuen Konfigurationsparameter "reserved_connections" festgelegten Pool. Die hiermit konfigurierte Anzahl von freien Slots steht exklusiv für entsprechende Rollen zur Verfügung. Die Summe aus "superuser_reserved_connections" und "reserved_connections" bestimmt, ob nur noch Verbindungen von entsprechenden Rollen oder Superuser zugelassen sind. Ist die Summe verfügbarer Slots kleiner oder gleich der reservierten Verbindungen, sind nur noch entsprechende Verbindungen dieser spezifischen Rollen erlaubt.
Mit "GRANT" lässt sich nun kontrollieren, ob mit einer Rolle explizit per "SET ROLE" in den Kontext einer anderen Rolle gewechselt werden darf. Dies war seither automatisch immer möglich, sobald Mitgliedschaft in einer entsprechenden Rolle gewährt wurde.
Mit dem neuen "GRANT .. SET" lässt sich dies nun unterbinden. Sind Rollenmitgliedschaften indirekt über mehrere Ebenen vergeben, so muss das "SET" für alle in der Hierarchie verwendeten Rollen gesetzt sein, in die der Nutzer wechseln möchte. Implizit vererbte Berechtigungen, etwa Berechtigungen auf Tabellen, sind hiervon natürlich nicht betroffen.
Die Datei "pg_hba.conf" erlaubt nun, Datenbank- und Rollennamen mittels regulärer Ausdrücke zu definieren. Damit lässt sich jetzt einfaches Pattern-Matching für die Zugangskontrolle anwenden.
Fazit
PostgreSQL 16 bietet wie gewohnt eine überwältigende Fülle an Verbesserungen und neuen Funktionen. Unterstrichen wird dies durch die Anzahl an Beiträgen zu diesem neuen Release. Weit über 300 Mitwirkende zählt PostgreSQL 16, viele nicht nur allein mit Beiträgen zum Code der freien Datenbank, sondern auch zu Dokumentation, Übersetzungen oder Fehlerbehebungen, aber auch Reviews von neuen Funktionen oder Tests.
Dies unterstreicht, wie wichtig die Community für die Pflege von PostgreSQL ist und auch noch so kleine Beiträge zum Erfolg dieses Projekts beitragen.