PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Partitionierungsverbesserungen in PostgreSQL 11

Ein Partitionierungssystem in PostgreSQL wurde erstmals in PostgreSQL 8.1 von 2ndQuadrant-Gründer Simon Riggs hinzugefügt . Es basierte auf der Vererbung von Beziehungen und verwendete eine neuartige Technik, um Tabellen vom Scannen durch eine Abfrage auszuschließen, die als „Einschränkungsausschluss“ bezeichnet wird. Während es damals ein großer Fortschritt war, wird es heutzutage als umständlich in der Verwendung sowie als langsam angesehen und muss daher ersetzt werden.

In Version 10 wurde es dank des heldenhaften Einsatzes von Amit Langote ersetzt mit moderner „deklarativer Partitionierung“. Diese neue Technologie bedeutete, dass Sie keinen Code mehr manuell schreiben mussten, um Tupel zu ihren richtigen Partitionen zu leiten, und dass Sie nicht länger manuell korrekte Einschränkungen für jede Partition deklarieren mussten:Das System erledigte diese Dinge automatisch für Sie.

Leider ist das in PostgreSQL 10 so ziemlich alles, was es getan hat. Aufgrund der schieren Komplexität und der Zeitbeschränkungen fehlten viele Dinge in der PostgreSQL 10-Implementierung. Robert Haas hielt einen Vortrag darüber in der Warschauer PGConf.EU.

Viele Leute haben daran gearbeitet, die Situation für PostgreSQL 11 zu verbessern; hier mein versuch einer nacherzählung. Ich habe diese in drei Bereiche aufgeteilt:

  1. Neue Partitionierungsfunktionen
  2. Bessere DDL-Unterstützung für partitionierte Tabellen
  3. Leistungsoptimierungen.

Neue Partitionierungsfunktionen

In PostgreSQL 10 können Ihre partitionierten Tabellen so in RANGE liegen und LISTE Modi. Dies sind leistungsstarke Tools, auf denen viele reale Datenbanken aufbauen können, aber für viele andere Designs benötigen Sie den neuen Modus, der in PostgreSQL 11 hinzugefügt wurde:HASH Partitionierung . Viele Kunden brauchen dies und Amul Sul hart gearbeitet, um es möglich zu machen. Hier ist ein einfaches Beispiel:

CREATE TABLE clients (
client_id INTEGER, name TEXT
) PARTITION BY HASH (client_id);

CREATE TABLE clients_0 PARTITION OF clients
    FOR VALUES WITH (MODULUS 3, REMAINDER 0);
CREATE TABLE clients_1 PARTITION OF clients
    FOR VALUES WITH (MODULUS 3, REMAINDER 1);
CREATE TABLE clients_2 PARTITION OF clients
    FOR VALUES WITH (MODULUS 3, REMAINDER 2);

Es ist nicht zwingend erforderlich, für alle Partitionen denselben Modulwert zu verwenden; Auf diese Weise können Sie später weitere Partitionen erstellen und die Zeilen bei Bedarf Partition für Partition neu verteilen.

Ein weiteres sehr nützliches Feature, geschrieben von Amit Khandekar ist die Möglichkeit UPDATE zuzulassen um Zeilen von einer Partition in eine andere zu verschieben — Das heißt, wenn sich die Werte der Partitionierungsspalte ändern, wird die Zeile automatisch in die richtige Partition verschoben. Zuvor hätte diese Operation einen Fehler ausgegeben.

Ein weiteres neues Feature, geschrieben von Amit Langote und mit freundlichen Grüßen , ist das INSERT ON CONFLICT UPDATE kann auf partitionierte Tabellen angewendet werden . Bisher schlug dieser Befehl fehl, wenn er auf eine partitionierte Tabelle abzielte. Sie könnten es zum Laufen bringen, indem Sie genau wissen, in welcher Partition die Zeile enden würde, aber das ist nicht sehr praktisch. Ich werde nicht auf die Details dieses Befehls eingehen, aber wenn Sie sich jemals gewünscht haben, Sie hätten UPSERT in Postgres ist es das. Ein Vorbehalt ist, dass das UPDATE Aktion darf die Zeile nicht in eine andere Partition verschieben.

Schließlich noch eine nette neue Funktion in PostgreSQL 11, diesmal von Jeevan Ladhe, Beena Emerson, Ashutosh Bapat, Rahila Syed, undRobert Haas ist Unterstützung für eine Standardpartition in einer partitionierten Tabelle , also eine Partition, die alle Zeilen aufnimmt, die in keine der regulären Partitionen passen. Obwohl diese Funktion auf dem Papier nett ist, ist sie in Produktionseinstellungen nicht sehr praktisch, da einige Vorgänge mit Standardpartitionen stärkere Sperren erfordern als ohne. Beispiel:Das Erstellen einer neuen Partition erfordert das Scannen der Standardpartition, um festzustellen, dass keine vorhandenen Zeilen mit den Grenzen der neuen Partition übereinstimmen. Vielleicht werden diese Sperranforderungen in Zukunft gesenkt, aber in der Zwischenzeit empfehle ich, sie nicht zu verwenden.

Bessere DDL-Unterstützung

In PostgreSQL 10 funktionierte bestimmte DDL nicht, wenn sie auf eine partitionierte Tabelle angewendet wurde, und verlangte von Ihnen, jede Partition einzeln zu verarbeiten. In PostgreSQL 11 haben wir einige dieser Einschränkungen behoben, wie zuvor von Simon Riggs angekündigt. Erstens können Sie jetzt CREATE INDEX verwenden auf einer partitionierten Tabelle , ein Feature, das von mir geschrieben wurde. Dies kann nur als eine Frage der Verringerung der Langeweile angesehen werden:Anstatt den Befehl für jede Partition zu wiederholen (und darauf zu achten, ihn nie für jede neue Partition zu vergessen), können Sie ihn nur einmal für die übergeordnete partitionierte Tabelle ausführen, und er wird automatisch angewendet auf alle vorhandenen und zukünftigen Partitionen.

Eine coole Sache, die man im Hinterkopf behalten sollte, ist der Abgleich bestehender Indizes in Partitionen. Wie Sie wissen, ist das Erstellen eines Indexes ein Blockadevorschlag, je weniger Zeit also in Anspruch genommen wird, desto besser. Ich habe diese Funktion so geschrieben, dass vorhandene Indizes in der Partition mit den erstellten Indizes verglichen werden, und wenn es Übereinstimmungen gibt, ist es nicht notwendig, die Partition zu scannen, um neue Indizes zu erstellen:Die vorhandenen Indizes würden verwendet.

Zusammen damit, ebenfalls von Ihnen, können Sie auch UNIQUE erstellen Beschränkungen sowie PRIMARY KEY Einschränkungen . Zwei Vorbehalte:Erstens muss der Partitionsschlüssel Teil des Primärschlüssels sein. Dadurch können die eindeutigen Prüfungen lokal pro Partition durchgeführt werden, wodurch globale Indizes vermieden werden. Zweitens ist es noch nicht möglich, Fremdschlüssel zu haben, die auf diese Primärschlüssel verweisen. Ich arbeite daran für PostgreSQL 12.

Eine andere Sache, die Sie tun können (dank derselben Person), ist erstellen Sie FOR EACH ROW Trigger auf eine partitionierte Tabelle , und lassen Sie es für alle Partitionen (vorhandene und zukünftige) gelten. Als Nebeneffekt können Sie eindeutig zurückstellen Einschränkungen für partitionierte Tabellen. Eine Einschränkung:nur NACH Trigger sind erlaubt, bis wir herausgefunden haben, wie wir mit BEFORE umgehen Trigger, die Zeilen in eine andere Partition verschieben.

Schließlich kann eine partitionierte Tabelle einen FOREIGN KEY haben Einschränkungen . Dies ist sehr praktisch, um große Faktentabellen zu partitionieren und gleichzeitig baumelnde Referenzen zu vermeiden, die jeder verabscheut. Mein Kollege Gabriele Bartolini packte mich am Schoß, als er herausfand, dass ich dies geschrieben und begangen hatte, und schrie, dass dies ein Wendepunkt sei und wie ich so unsensibel sein könnte, ihn nicht darüber zu informieren. Ich hacke den Code nur zum Spaß weiter.

Aufführungsarbeit

Früher war die Vorverarbeitung von Abfragen, um herauszufinden, welche Partitionen nicht gescannt werden sollen (Einschränkungsausschluss), ziemlich einfach und langsam. Dies wurde durch die bewundernswerte Teamarbeit von Amit Langote, David Rowley, Beena Emerson und Dilip Kumar verbessert, um zuerst das „schnellere Pruning“ und danach das darauf basierende „Runtime Pruning“ einzuführen. Das Ergebnis ist viel leistungsfähiger und schneller (David Rowley habe dies bereits in einem früheren Artikel beschrieben.) Nach all diesem Aufwand wird Partition Pruning an drei Stellen im Leben einer Abfrage angewendet:

  1. Zur Zeit des Abfrageplans
  2. Wenn die Abfrageparameter empfangen werden,
  3. An jedem Punkt, an dem ein Abfrageknoten Werte als Parameter an einen anderen Knoten übergibt.

Dies ist eine bemerkenswerte Verbesserung gegenüber dem ursprünglichen System, das nur zum Zeitpunkt des Abfrageplans angewendet werden konnte, und ich glaube, es wird vielen gefallen.

Sie können diese Funktion in Aktion sehen, indem Sie die EXPLAIN-Ausgabe für eine Abfrage vor und nach dem Deaktivieren von enable_partition_pruning vergleichen Möglichkeit. Vergleichen Sie als sehr vereinfachtes Beispiel diesen Plan ohne Beschneidung:

SET enable_partition_pruning TO off;
EXPLAIN (ANALYZE, COSTS off)
SELECT * FROM clientes
WHERE cliente_id = 1234;
                                QUERY PLAN                                
-------------------------------------------------------------------------
 Append (actual time=6.658..10.549 rows=1 loops=1)
   ->  Seq Scan on clientes_1 (actual time=4.724..4.724 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 24978
   ->  Seq Scan on clientes_00 (actual time=1.914..1.914 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12644
   ->  Seq Scan on clientes_2 (actual time=0.017..1.021 rows=1 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12570
   ->  Seq Scan on clientes_3 (actual time=0.746..0.746 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12448
   ->  Seq Scan on clientes_01 (actual time=0.648..0.648 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12482
   ->  Seq Scan on clientes_4 (actual time=0.774..0.774 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12400
   ->  Seq Scan on clientes_5 (actual time=0.717..0.717 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12477
 Planning Time: 0.375 ms
 Execution Time: 10.603 ms

mit dem durch Beschneiden erzeugten:

EXPLAIN (ANALYZE, COSTS off)
SELECT * FROM clientes
WHERE cliente_id = 1234;
                                QUERY PLAN                               
----------------------------------------------------------------------
 Append (actual time=0.054..2.787 rows=1 loops=1)
   ->  Seq Scan on clientes_2 (actual time=0.052..2.785 rows=1 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12570
 Planning Time: 0.292 ms
 Execution Time: 2.822 ms

Ich bin sicher, Sie werden das überzeugend finden. Sie können eine Menge ausgefeilterer Beispiele sehen, indem Sie die Datei mit den erwarteten Regressionstests durchlesen.

Ein weiterer Punkt war die Einführung partitionsweiser Joins von Assutosh Bapat . Die Idee dabei ist, dass Sie, wenn Sie zwei partitionierte Tabellen haben und diese auf identische Weise partitioniert sind, beim Verbinden jede Partition auf der einen Seite mit der passenden Partition auf der anderen Seite verknüpfen können. Dies ist viel besser, als jede Partition auf der einen Seite mit jeder Partition auf der anderen Seite zu verbinden. Die Tatsache, dass die Partitionsschemata exakt übereinstimmen müssen mag es unwahrscheinlich erscheinen lassen, dass dies in der realen Welt von großem Nutzen ist, aber in Wirklichkeit gibt es viele Situationen, in denen dies zutrifft. Beispiel:eine Orders-Tabelle und die entsprechende Orders_items-Tabelle. Zum Glück gibt es bereits viel Arbeit, um diese Einschränkung zu lockern.

Der letzte Punkt, den ich erwähnen möchte, sind Partitionwise Aggregates von Jeevan Chalke, Ashutosh Bapat, undRobert Haas . Diese Optimierung bedeutet, dass eine Aggregation, die die Partitionsschlüssel in der GROUP BY enthält -Klausel kann ausgeführt werden, indem die Zeilen jeder Partition separat aggregiert werden, was viel schneller ist.

Abschlussgedanken

Nach den bedeutenden Entwicklungen in diesem Zyklus hat PostgreSQL eine viel überzeugendere Partitionierungsgeschichte. Obwohl noch viele Verbesserungen vorgenommen werden müssen, insbesondere um die Leistung und Parallelität verschiedener Vorgänge mit partitionierten Tabellen zu verbessern, sind wir jetzt an einem Punkt angelangt, an dem die deklarative Partitionierung zu einem sehr wertvollen Tool für viele Anwendungsfälle geworden ist. Bei 2ndQuadrant werden wir weiterhin Code beitragen, um PostgreSQL in diesem und anderen Bereichen zu verbessern, so wie wir es seit 8.0 für jede einzelne Version getan haben.