Zuvor hatte ich einen Blog über partitionsweisen Join in PostgreSQL geschrieben. In diesem Blog hatte ich über eine erweiterte Partitionsabgleichstechnik gesprochen, die es ermöglicht, partitionsweises Join in mehr Fällen zu verwenden. In diesem Blog werden wir diese Technik im Detail besprechen.
Zusammenfassend lässt sich sagen, dass die grundlegende Partitionsanpassungstechnik eine Verknüpfung zwischen zwei partitionierten Tabellen unter Verwendung einer partitionsweisen Join-Technik ermöglicht, wenn die beiden partitionierten Tabellen genau übereinstimmende Partitionsgrenzen haben, z. partitionierte Tabellen prt1 und prt2, die unten beschrieben werden
psql> \d+ prt1
... [output clipped]
Partition key: RANGE (a)
Partitions: prt1_p1 FOR VALUES FROM (0) TO (5000),
prt1_p2 FOR VALUES FROM (5000) TO (15000),
prt1_p3 FOR VALUES FROM (15000) TO (30000)
und
psql>\d+ prt2
... [ output clipped ]
Partition key: RANGE (b)
Partitions: prt2_p1 FOR VALUES FROM (0) TO (5000),
prt2_p2 FOR VALUES FROM (5000) TO (15000),
prt2_p3 FOR VALUES FROM (15000) TO (30000)
Ein Join zwischen prt1 und prt2 auf ihrem Partitionsschlüssel (a) wird in Joins zwischen ihren übereinstimmenden Partitionen unterteilt, d. h. prt1_p1 verbindet prt2_p1, prt1_p2 verbindet prt2_p2 und prt1_p3 verbindet prt2_p3. Die Ergebnisse dieser drei Joins bilden zusammen das Ergebnis des Joins zwischen prt1 und prt2. Dies hat viele Vorteile, wie in meinem vorherigen Blog besprochen. Der grundlegende Partitionsabgleich kann jedoch nicht zwei partitionierte Tabellen mit unterschiedlichen Partitionsgrenzen verbinden. Wenn im obigen Beispiel prt1 eine zusätzliche Partition prt1_p4 FÜR WERTE VON (30000) BIS (50000) hat, würde der grundlegende Partitionsabgleich nicht helfen, eine Verknüpfung zwischen prt1 und prt2 in eine partitionsbezogene Verknüpfung umzuwandeln, da sie keine genau übereinstimmende Partition haben Grenzen.
Viele Anwendungen verwenden Partitionen, um aktiv verwendete Daten und veraltete Daten zu trennen, eine Technik, die ich in meinem anderen Blog besprochen habe. Die veralteten Daten werden schließlich durch Löschen von Partitionen entfernt. Neue Partitionen werden erstellt, um neue Daten aufzunehmen. Ein Join zwischen zwei solchen partitionierten Tabellen verwendet meistens einen partitionsweisen Join, da sie die meiste Zeit übereinstimmende Partitionen haben. Aber wenn eine aktive Partition zu einer dieser Tabellen hinzugefügt oder eine veraltete gelöscht wird, stimmen ihre Partitionsgrenzen nicht überein, bis die andere Tabelle ebenfalls einer ähnlichen Operation unterzogen wird. Während dieses Intervalls verwendet ein Join zwischen diesen beiden Tabellen keinen partitionsweisen Join und kann ungewöhnlich lange dauern, bis er ausgeführt wird. Wir möchten nicht, dass ein Join, der während dieser kurzen Zeit auf die Datenbank trifft, eine schlechte Leistung erbringt, da er keinen partitionsweisen Join verwenden kann. Der erweiterte Partitionsabgleichsalgorithmus hilft in diesem und in komplizierteren Fällen, in denen Partitionsgrenzen nicht genau übereinstimmen.
Erweiterter Partitionsabgleichalgorithmus
Die erweiterte Partitionsabgleichstechnik findet übereinstimmende Partitionen aus zwei partitionierten Tabellen, selbst wenn ihre Partitionsgrenzen nicht genau übereinstimmen. Es findet übereinstimmende Partitionen, indem es die Grenzen aus beiden Tabellen in ihrer sortierten Reihenfolge vergleicht, ähnlich wie beim Merge-Join-Algorithmus. Zwei beliebige Partitionen, eine von jeder der partitionierten Tabelle, deren Grenzen genau übereinstimmen oder sich überlappen, werden als Joining-Partner betrachtet, da sie Joining-Zeilen enthalten können. Um mit dem obigen Beispiel fortzufahren, nehmen wir an, eine aktive neue Partition prt2_p4 wird zu prt4 hinzugefügt. Die partitionierten Tabellen sehen jetzt so aus:
psql>\d+ prt1
... [output clipped]
Partition key: RANGE (a)
Partitions: prt1_p1 FOR VALUES FROM (0) TO (5000),
prt1_p2 FOR VALUES FROM (5000) TO (15000),
prt1_p3 FOR VALUES FROM (15000) TO (30000)
und
psql>\d+ prt2
... [ output clipped ]
Partition key: RANGE (b)
Partitions: prt2_p1 FOR VALUES FROM (0) TO (5000),
prt2_p2 FOR VALUES FROM (5000) TO (15000),
prt2_p3 FOR VALUES FROM (15000) TO (30000),
prt2_p4 FOR VALUES FROM (30000) TO (50000)
Es ist leicht zu erkennen, dass die Partitionsgrenzen von prt1_p1 und prt2_p1, prt1_p2 und prt2_p2 sowie prt1_p3 und prt2_p3 übereinstimmen. Aber im Gegensatz zum einfachen Partitionsabgleich weiß der erweiterte Partitionsabgleich, dass prt2_p4 keine übereinstimmende Partition in prt1 hat. Wenn der Join zwischen prt1 und prt2 ein INNER-Join ist oder wenn prt2 eine INNER-Relation im Join ist, enthält das Join-Ergebnis keine Zeile von prt2_p4. Aktiviert mit detaillierten Informationen über die übereinstimmenden Partitionen und Partitionen, die nicht übereinstimmen, im Gegensatz dazu, ob Partitionsgrenzen übereinstimmen oder nicht, kann der Abfrageoptimierer entscheiden, ob partitionsweises Join verwendet wird oder nicht. In diesem Fall wird der Join als Join zwischen den übereinstimmenden Partitionen ausgeführt, wobei prt2_p4 beiseite gelassen wird. Aber das ist nicht viel wie ein "erweiterter" Partitionsabgleich. Sehen wir uns diesmal einen etwas komplizierteren Fall an, in dem Listen-partitionierte Tabellen verwendet werden:
psql>\d+ plt1
Partition key: LIST (c)
Partitions: plt1_p1 FOR VALUES IN ('0001', '0003'),
plt1_p2 FOR VALUES IN ('0004', '0006'),
plt1_p3 FOR VALUES IN ('0008', '0009')
und
psql>\d+ plt2
Partition key: LIST (c)
Partitions: plt2_p1 FOR VALUES IN ('0002', '0003'),
plt2_p2 FOR VALUES IN ('0004', '0006'),
plt2_p3 FOR VALUES IN ('0007', '0009')
Beachten Sie, dass es in beiden Relationen genau drei Partitionen gibt, die Wertlisten der Partitionen jedoch unterschiedlich sind. Die der Partition plt1_p2 entsprechende Liste stimmt genau mit der von plt2_p2 überein. Abgesehen davon haben keine zwei Partitionen, eine von jeder Seite, genau übereinstimmende Listen. Der erweiterte Partitionsabgleichsalgorithmus leitet ab, dass plt1_p1 und plt2_p1 überlappende Listen haben und ihre Listen sich nicht mit anderen Partitionen aus der anderen Relation überschneiden. Ähnlich für plt1_p3 und plt2_p3. Der Abfrageoptimierer sieht dann, dass der Join zwischen plt1 und plt2 als partitionsweiser Join ausgeführt werden kann, indem die übereinstimmenden Partitionen, d. h. plt1_p1 und plt2_p1, plt1_p2 und plt2_p2 bzw. plt1_p3 und plt2_p3, verknüpft werden. Der Algorithmus kann übereinstimmende Partitionen in noch komplexeren partitionsgebundenen Sätzen von Listen sowie bereichspartitionierten Tabellen finden. Aber wir werden sie der Kürze halber nicht behandeln. Interessierte und mutigere Leser können einen Blick in das Commit werfen. Es enthält auch viele Testfälle, die verschiedene Szenarien zeigen, in denen ein erweiterter Partitionsabgleichalgorithmus verwendet wird.
Einschränkungen
Outer Joins mit fehlenden passenden Partitionen auf der Innenseite
Outer Joins stellen ein besonderes Problem in der PostgreSQL-Welt dar. Betrachten Sie prt2 LEFT JOIN prt1 im obigen Beispiel, wobei prt2 eine OUTER-Relation ist. prt2_p4 hat keinen Joining-Partner in prt1 und dennoch sollten die Zeilen in dieser Partition Teil des Join-Ergebnisses sein, da sie zur äußeren Beziehung gehören. Wenn in PostgreSQL die INNER-Seite eines Joins leer ist, wird sie durch eine „Dummy“-Relation dargestellt, die keine Zeilen ausgibt, aber dennoch das Schema dieser Relation kennt. Normalerweise entsteht eine "Dummy"-Beziehung aus einer Nicht-Dummy-Beziehung, die aufgrund einer Abfrageoptimierung wie dem Ausschluss von Einschränkungen keine Zeilen ausgeben wird. Der Abfrageoptimierer von PostgreSQL markiert eine solche Nicht-Dummy-Beziehung als Dummy und der Executor fährt normal fort, wenn er einen solchen Join ausführt. Aber wenn es keine passende innere Partition für eine äußere Partition gibt, gibt es keine "existierende Entität", die als "Dummy" markiert werden kann. Beispielsweise gibt es in diesem Fall kein prt1_p4, das eine innere Dummy-Partition darstellen kann, die mit dem äußeren prt2_p4 verbunden ist. Im Moment hat PostgreSQL keine Möglichkeit, solche „Dummy“-Beziehungen während der Planung zu „erstellen“. Daher verwendet der Abfrageoptimierer in diesem Fall keinen partitionsweisen Join.
Im Idealfall erfordert ein solcher Join mit leerem Inner nur ein Schema der inneren Relation und nicht eine ganze Relation. Dieses Schema kann von der partitionierten Tabelle selbst abgeleitet werden. Alles, was es braucht, ist die Fähigkeit, die Join-Zeile zu erzeugen, indem die Spalten aus einer Zeile auf der Außenseite verwendet werden, die durch NULL-Werte für die Spalten auf der Innenseite verbunden sind. Sobald wir diese Fähigkeit in PostgreSQL haben, wird der Abfrageoptimierer auch in diesen Fällen partitionsweisen Join verwenden können.
Lassen Sie mich betonen, dass die äußeren Joins, bei denen es keine fehlenden Partitionen auf dem inneren Join gibt, einen partitionsweisen Join verwenden.
Mehrere übereinstimmende Partitionen
Wenn die Tabellen so partitioniert sind, dass mehrere Partitionen von einer Seite mit einer oder mehreren Partitionen auf der anderen Seite übereinstimmen, kann partitionsweises Join nicht verwendet werden, da es keine Möglichkeit gibt, während der Planungszeit eine "Append"-Beziehung zu induzieren, die zwei oder mehr darstellt Partitionen zusammen. Hoffentlich werden wir auch diese Einschränkung irgendwann aufheben und die partitionsweise Verknüpfung auch in diesen Fällen zulassen.
Hash-partitionierte Tabellen
Partitionsgrenzen von zwei Hash-partitionierten Tabellen, die denselben Modulo verwenden, stimmen immer überein. Wenn das Modulo unterschiedlich ist, kann eine Zeile aus einer bestimmten Partition einer Tabelle ihre Joining-Partner in vielen Partitionen der anderen Tabelle haben, sodass eine bestimmte Partition von einer Seite mit mehreren Partitionen der anderen Tabelle übereinstimmt, wodurch die partitionsweise Verknüpfung unwirksam wird.
Wenn der erweiterte Partitionsabgleichalgorithmus keine übereinstimmenden Partitionen findet oder die partitionsweise Verknüpfung aufgrund der oben genannten Einschränkungen nicht verwendet werden kann, greift PostgreSQL zurück, um die partitionierten Tabellen als reguläre Tabellen zu verknüpfen.
Erweiterte Partitionsanpassungszeit
Simon sprach einen interessanten Punkt an, als er das Feature kommentierte. Partitionen einer partitionierten Tabelle ändern sich nicht oft, daher sollte das Ergebnis des erweiterten Partitionsabgleichs über einen längeren Zeitraum gleich bleiben. Sie jedes Mal zu berechnen, wenn eine Abfrage mit diesen Tabellen ausgeführt wird, ist unnötig. Stattdessen könnten wir den Satz übereinstimmender Partitionen in einem Katalog speichern und ihn jedes Mal aktualisieren, wenn sich die Partitionen ändern. Das ist etwas Arbeit, aber es lohnt sich, die Zeit damit zu verbringen, die Partition für jede Abfrage abzugleichen.
Trotz all dieser Einschränkungen haben wir heute eine sehr nützliche Lösung, die den meisten praktischen Fällen gerecht wird. Unnötig zu sagen, dass diese Funktion nahtlos mit FDW-Join-Pushdown zusammenarbeitet und die Sharding-Fähigkeiten verbessert, die PostgreSQL bereits hat!