SQLProgrammierungSenior Datenbankingenieur

An welchem genauen Schwellenwert in der Phase der Abfrageoptimierung bestimmt der Planner von **PostgreSQL**, dass ein **Gather Merge**-Knoten einem **Gather**-Knoten zur parallelen Ergebniszusammenführung vorzuziehen ist, und welches spezifische Merkmal der zugrunde liegenden Scanknoten diktiert diese Auswahl?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort auf die Frage.

Die Einführung der parallelen Abfragefähigkeiten in PostgreSQL 9.6 brachte den Gather-Knoten, um Ergebnisse von Hintergrundarbeitern in den Führungsprozess zu kombinieren. Jedoch zerstört der standardmäßige Gather-Knoten jede Tupelreihenfolge, die von den parallelen Arbeitern erzeugt wird, was einen kostspieligen finalen Sort-Schritt im Führungsprozess erforderlich macht, um die Reihenfolge wiederherzustellen. Um diese Redundanz bei der Verarbeitung von von Natur aus geordneten Datenströmen zu beseitigen, führte Version 10 den Gather Merge-Knoten ein, der eine k-fach Zusammenführung sortierter Eingaben von Arbeitern durchführt und die Notwendigkeit einer Materialisierung und Sortierung auf der Seite des Führungsprozesses umgeht.

Der Planner wählt die Injektion von Gather Merge ausschließlich dann aus, wenn der parallele Unterplan garantiert, dass die Ausgabe gemäß einer erforderlichen Eigenschaft geordnet ist, die typischerweise durch Index Scans oder Merge Joins erzeugt wird, die die Tupelreihenfolge beibehalten. Wenn der Unterplan die Ordnung durch Operationen wie Hash Joins oder ungeordnete Aggregationen verliert, wird Gather Merge unzulässig, was den Optimierer zwingt, zwischen einem Gather gefolgt von einem kostspieligen Sort oder der vollständigen Aufgabe des Parallelismus zu wählen, um die Ordnung mit einem einzelnen Prozess aufrechtzuerhalten.

Wenn der Unterplan eine geordnete Ausgabe garantiert, ermöglicht der Gather Merge dem Führungsprozess, eine Streaming-Zusammenführung unter Verwendung minimaler Speicherpuffer durchzuführen, anstatt alle Tupel zu materialisieren und zu sortieren. Die Speicherstrategie verändert sich von einer einzigen großen Zuweisung zum Sortieren im Führungsprozess hin zu kleineren, pro Arbeiter verwalteten sortierten Abläufen, wodurch das Risiko einer Erschöpfung von work_mem und von Datenträgerüberläufen während großangelegter geordneter Abrufe erheblich verringert wird.

Situation aus dem Leben

Unser Team verwaltete eine Plattform für zeitbasierte Analysen, die Sensordaten in einer PostgreSQL-Tabelle speichert, die nach Stunden partitioniert ist und mehr als 2 Milliarden Zeilen enthält. Ein wichtiges Dashboard musste die neuesten 1000 Messungen über alle Partitionen, absteigend nach timestamp, anzeigen und hatte ein Latenzbudget von unter 500 Millisekunden. Der anfängliche, einprozessige Abfrageplan erfüllte diese Anforderungen nicht und erzeugte einen Engpass im Benutzererlebnis während der Spitzenanalysebelastungen.

Einprozessiger Index-Scan: Wir hatten zunächst in Betracht gezogen, einen rückwärts gerichteten Index-Scan auf jeder Partition durchzuführen, gefolgt von einem Limit-Knoten, der sequenziell ausgeführt wird. Dieser Ansatz bot eine einfache Implementierung und deterministische Reihenfolge ohne komplexe parallele Koordination. Er konnte jedoch nicht die I/O-Bandbreite unseres NVMe-Speicherarrays ausnutzen und überschritt konsequent 2 Sekunden während der Spitzenbelastung, was ihn für Echtzeitanalysen unakzeptabel machte.

Paralleler Seq Scan mit Gather und Sort: Der zweite Ansatz bestand darin, max_parallel_workers_per_gather zu aktivieren und einen Parallel Seq Scan mit einem standardmäßigen Gather-Knoten zu verwenden, der alle Zeilen in den Führungsprozess für ein abschließendes Sort und Limit sammelte. Dies nutzte den CPU-Parallelismus und verbesserte den Durchsatz des Scans erheblich. Dennoch führte es dazu, dass der Führungsprozess über 4 GB work_mem für die Sortierung Millionen von Zeilen zuteilen musste, was häufig zu Datenträgerüberläufen und OutOfMemory-Fehlern auf unserem eingeschränkten Führungsprozess führte und die Systemstabilität gefährdete.

Paralleler Index-Scan mit Gather Merge: Letztendlich wählten wir einen Plan, bei dem die Arbeiter Parallele Index-Scans in absteigender Reihenfolge des Zeitstempels durchführten, die in einen Gather Merge-Knoten eingespeist wurden. Die Arbeiter scannen die Indexblattseiten in der erforderlichen Reihenfolge und streamen die sortierten Tupel an den Führungsprozess, der eine leichte k-fach Zusammenführung durchführt, um die obersten 1000 Zeilen zu extrahieren. Diese Architektur beseitigte die Notwendigkeit eines abschließenden Sortierens im Führungsprozess, wodurch der Speicherbedarf drastisch verringert wurde und die Streaming-Effizienz erhalten blieb.

Wir wählten den Gather Merge-Ansatz, weil er sowohl die Latenz- als auch die Speicheranforderungen einmalig erfüllte, indem er die vorhandene Indexstruktur nutzte, anstatt sie mit hashbasierten Operationen zu bekämpfen. Diese Lösung reduzierte den Speicherbedarf des Führungsprozesses auf unter 64 MB für die Zusammenführungs-Puffer und erzielte konsistent Reaktionszeiten von unter 300 ms. Das System bewältigt nun Spitzenlasten ohne Speichererschöpfung, was die architektonische Entscheidung validiert, die Ordnung durch parallele Ausführung zu bewahren.

Was Kandidaten oft übersehen

Warum führt die Platzierung eines Hash Aggregate unterhalb eines Gather Merge-Knotens dazu, dass der Planner von PostgreSQL den Plan entweder ablehnt oder einen expliziten Sort-Schritt einfügt, und wie unterscheidet sich dies vom Verhalten des GroupAggregate?

Hash Aggregate baut eine ungeordnete Hash-Tabelle auf, um Tupel zu gruppieren, was von Natur aus jede Eingabereihenfolge zerstört, die von zugrunde liegenden Scans erzeugt wurde. Da Gather Merge streng geordnete Eingabeströme von allen parallelen Arbeitern benötigt, um seine Streaming-k-fach Zusammenführung durchzuführen, blockiert die ungeordnete Ausgabe von Aggregationen die direkte Nutzung. Im Gegensatz dazu kann GroupAggregate mit vorausgewählten Eingaben arbeiten und die Tupelreihenfolge beibehalten, wenn die GROUP BY-Schlüssel mit der Sortierreihenfolge übereinstimmen, wodurch es mit Gather Merge kompatibel wird, ohne dass ein Zwischensortierschritt erforderlich ist.

Wie beeinflusst die parallel_tuple_cost GUC den Schwellenwert, bei dem der Planner von einem Gather-Plan zu einem Gather Merge-Plan wechselt, wenn die Kosten für die Zusammenführung sortierter Streams von acht parallelen Arbeitern geschätzt werden?

parallel_tuple_cost fügt eine pro-Tupel CPU-Überlastung für den Transfer von Zeilen zwischen parallelen Arbeitern und dem Führungsprozess hinzu. Für Gather Merge ist diese Kosten etwas höher als für einen standardmäßigen Gather-Knoten aufgrund der zusätzlichen Vergleichslogik, die erforderlich ist, um den Zusammenführungs-Heap aufrechtzuerhalten. Wenn die geschätzte Ergebnismenge klein ist, könnte der Planner einen Gather-Knoten zusammen mit einem preiswerten Sort im Führungsprozess gegenüber Gather Merge bevorzugen, da die kumulierte Überlast acht Zusammenführungsströme die Kosten eines zentralen Sortierens einer kleinen Gruppe von Tupeln übersteigen kann.

Welche spezifische Einschränkung tritt auf, wenn DECLARE CURSOR mit der SCROLL-Option über einen Abfrageplan verwendet wird, der einen Gather Merge-Knoten enthält, und warum könnte der Executor stillschweigend die gesamte Ergebnismenge materialisieren, trotz der Streaming-Natur der Zusammenführung?

SCROLL-Cursors erfordern die Fähigkeit, rückwärts durch die Ergebnismenge zu navigieren, was die Materialisierung von Zeilen im work_mem oder das Auslagern auf die Festplatte erfordert, um das Rückwärtsabrufen zu unterstützen. Obwohl Gather Merge effizient eine Streaming- und geordnete Ausgabe produziert, zwingt die SCROLL-Option den Executor dazu, einen Materialize-Knoten über dem Gather Merge einzufügen, um Zeilen für potenzielle rückwärtsgerichtete Traversierungen zu puffern. Diese Materialisierung verbraucht Speicher im Verhältnis zur Größe der Ergebnismenge, was effektiv die Vorteile der Speicher-Effizienzstrategien der Streaming-Zusammenführung negiert und potenziell Datenträgerüberläufe verursacht, die durch die ursprüngliche Wahl von Gather Merge vermieden wurden.