Wcześniej pisałem blog o łączeniu partycji w PostgreSQL. W tym blogu mówiłem o zaawansowanej technice dopasowywania partycji, która pozwoli na użycie łączenia partycji w większej liczbie przypadków. W tym blogu szczegółowo omówimy tę technikę.
Podsumowując, podstawowa technika dopasowywania partycji umożliwia wykonanie łączenia między dwiema partycjonowanymi tabelami przy użyciu techniki łączenia partycjonowanego, jeśli dwie partycjonowane tabele mają dokładnie pasujące granice partycji, np. podzielone na partycje tabele prt1 i prt2 opisane poniżej
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)
i
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)
Łączenie między prt1 i prt2 w ich kluczu partycji (a) jest podzielone na sprzężenia między pasującymi partycjami, tj. przyłączenia prt1_p1 prt2_p1, prt1_p2 przyłączenia prt2_p2 i prt1_p3 przyłączenia prt2_p3. Wyniki tych trzech złączeń razem tworzą wynik złączenia między prt1 i prt2. Ma to wiele zalet, o których wspominałem na moim poprzednim blogu. Jednak podstawowe dopasowanie partycji nie może łączyć dwóch partycjonowanych tabel z różnymi granicami partycji. W powyższym przykładzie, jeśli prt1 ma dodatkową partycję prt1_p4 FOR VALUES FROM (30000) TO (50000), podstawowe dopasowanie partycji nie pomogłoby w przekonwertowaniu połączenia między prt1 i prt2 na sprzężenie oparte na partycjach, ponieważ nie mają one dokładnie pasującej partycji granice.
Wiele aplikacji używa partycji do segregowania aktywnie używanych danych i przestarzałych danych, technikę, którą omówiłem na moim innym blogu. Nieaktualne dane są ostatecznie usuwane przez upuszczenie partycji. Tworzone są nowe partycje, aby pomieścić świeże dane. Łączenie między dwiema takimi podzielonymi na partycje tabelami będzie w większości wykorzystywać łączenie partycjonowane, ponieważ przez większość czasu będą miały pasujące partycje. Ale gdy aktywna partycja zostanie dodana do jednej z tych tabel lub przestarzała zostanie usunięta, ich granice partycji nie będą się zgadzać, dopóki druga tabela również nie przejdzie podobnej operacji. Podczas tego interwału łączenie między tymi dwiema tabelami nie będzie korzystać z łączenia partycjonującego i jego wykonanie może zająć niezwykle dużo czasu. Nie chcemy, aby złączenie trafiające do bazy danych w tym krótkim czasie nie działało źle, ponieważ nie może korzystać z łączenia partycjonującego. Zaawansowany algorytm dopasowywania partycji pomaga w tych i bardziej skomplikowanych przypadkach, w których granice partycji nie pasują dokładnie.
Zaawansowany algorytm dopasowywania partycji
Zaawansowana technika dopasowywania partycji znajduje pasujące partycje z dwóch partycjonowanych tabel, nawet jeśli ich granice partycji nie są dokładnie dopasowane. Znajduje pasujące partycje, porównując granice z obu tabel w kolejności posortowania, podobnie jak algorytm łączenia scalającego. Dowolne dwie partycje, po jednej z każdej z podzielonych na partycje tabeli, których granice dokładnie pasują lub nakładają się, są uważane za partnerów łączących, ponieważ mogą zawierać łączące się wiersze. Kontynuując powyższy przykład, powiedzmy, że aktywna nowa partycja prt2_p4 zostanie dodana do prt4. Tabele podzielone na partycje wyglądają teraz tak:
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)
i
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)
Łatwo zauważyć, że granice partycji odpowiednio prt1_p1 i prt2_p1, prt1_p2 i prt2_p2 oraz prt1_p3 i prt2_p3 są zgodne. Ale w przeciwieństwie do podstawowego dopasowywania partycji, zaawansowane dopasowywanie partycji będzie wiedziało, że prt2_p4 nie ma żadnej pasującej partycji w prt1. Jeśli złączenie między prt1 i prt2 jest złączeniem INNER lub gdy prt2 jest relacją INNER w złączeniu, wynik złączenia nie będzie zawierał żadnego wiersza z prt2_p4. Włączony ze szczegółowymi informacjami o pasujących partycjach i partycjach, które nie pasują, w porównaniu tylko z tym, czy granice partycji są zgodne, czy nie, optymalizator zapytań może zdecydować, czy użyć sprzężenia partycji, czy nie. W takim przypadku zdecyduje się wykonać łączenie jako łączenie między pasującymi partycjami, pozostawiając prt2_p4 na boku. Ale to nie przypomina „zaawansowanego” dopasowywania partycji. Zobaczmy tym razem nieco bardziej skomplikowany przypadek użycia tabel z partycjami list:
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')
i
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')
Zauważ, że w obu relacjach są dokładnie trzy partycje, ale listy wartości partycji różnią się. Lista odpowiadająca partycji plt1_p2 odpowiada dokładnie liście plt2_p2. Poza tym żadne dwie partycje, jedna z każdej strony, nie mają dokładnie pasujących list. Zaawansowany algorytm dopasowywania partycji wnioskuje, że plt1_p1 i plt2_p1 mają nakładające się listy, a ich listy nie pokrywają się z żadną inną partycją z innej relacji. Podobnie w przypadku plt1_p3 i plt2_p3. Optymalizator zapytań widzi następnie, że połączenie między plt1 i plt2 można wykonać jako sprzężenie partycji, łącząc pasujące partycje, tj. odpowiednio plt1_p1 i plt2_p1, plt1_p2 i plt2_p2 oraz plt1_p3 i plt2_p3. Algorytm może znaleźć pasujące partycje w jeszcze bardziej złożonych zestawach list powiązanych z partycjami, a także w tabelach podzielonych na partycje zakresów. Ale nie będziemy ich omawiać ze względu na zwięzłość. Zainteresowani i odważniejsi czytelnicy mogą rzucić okiem na zobowiązanie. Ma również wiele przypadków testowych, które pokazują różne scenariusze, w których używany jest zaawansowany algorytm dopasowywania partycji.
Ograniczenia
Zewnętrzne połączenia z pasującymi przegrodami po wewnętrznej stronie
Złączenia zewnętrzne stanowią szczególny problem w świecie PostgreSQL. Rozważmy prt2 LEFT JOIN prt1, w powyższym przykładzie, gdzie prt2 jest relacją OUTER. prt2_p4 nie ma partnera łączącego w prt1, a mimo to wiersze w tej partycji powinny być częścią wyniku łączenia, ponieważ należą do relacji zewnętrznej. W PostgreSQL, gdy wewnętrzna strona złączenia jest pusta, jest reprezentowana przez relację „fałszywą”, która nie emituje wierszy, ale nadal zna schemat tej relacji. Zwykle relacja „fałszywa” wyłania się z relacji niefikcyjnej, która nie będzie emitować żadnych wierszy z powodu pewnej optymalizacji zapytań, takiej jak wykluczenie ograniczeń. Optymalizator zapytań PostgreSQL oznacza taką niefikcyjną relację jako dummy, a executor postępuje normalnie podczas wykonywania takiego złączenia. Ale gdy nie ma pasującej partycji wewnętrznej dla partycji zewnętrznej, nie ma "istniejącej jednostki", którą można oznaczyć jako "dummy". Na przykład w tym przypadku nie istnieje prt1_p4, który może reprezentować fikcyjną wewnętrzną partycję łączącą zewnętrzną prt2_p4. W tej chwili PostgreSQL nie ma możliwości „tworzenia” takich „fikcyjnych” relacji podczas planowania. Dlatego optymalizator zapytań nie używa w tym przypadku złączenia partycjonującego.
Idealnie takie złączenie z pustym wewnętrznym wymaga jedynie schematu relacji wewnętrznej, a nie całej relacji. Ten schemat można wyprowadzić z samej tabeli partycjonowanej. Wszystko czego potrzebuje to możliwość utworzenia wiersza sprzężenia przy użyciu kolumn z wiersza po zewnętrznej stronie połączonych wartościami NULL dla kolumn od strony wewnętrznej. Gdy mamy taką możliwość w PostgreSQL, optymalizator zapytań będzie mógł używać złączenia partycjonującego nawet w takich przypadkach.
Chciałbym podkreślić, że łączenia zewnętrzne, w których nie ma brakujących partycji na złączeniu wewnętrznym, używają złączenia partycjonującego.
Wiele pasujących partycji
Gdy tabele są podzielone na partycje w taki sposób, że wiele partycji z jednej strony pasuje do jednej lub więcej partycji po drugiej stronie, nie można użyć łączenia partycji, ponieważ nie ma sposobu na wywołanie relacji „Dołącz” podczas planowania, która reprezentuje dwie lub więcej przegrody razem. Mamy nadzieję, że kiedyś usuniemy to ograniczenie i zezwolimy na użycie łączenia partycji również w tych przypadkach.
Hash partycjonowane tabele
Granice partycji dwóch tabel z partycjami mieszanymi przy użyciu tego samego modulo są zawsze zgodne. Gdy modulo jest inny, wiersz z danej partycji jednej tabeli może mieć partnerów łączących się w wielu partycjach drugiej, w ten sposób dana partycja z jednej strony pasuje do wielu partycji drugiej tabeli, co powoduje, że łączenie partycji jest nieefektywne.
Gdy zaawansowany algorytm dopasowywania partycji nie znajdzie pasujących partycji lub nie można użyć łączenia partycji z powodu powyższych ograniczeń, PostgreSQL powraca do łączenia tabel partycjonowanych jako zwykłych tabel.
Zaawansowany czas dopasowania partycji
Simon poruszył ciekawą kwestię, komentując tę funkcję. Partycje tabeli partycjonowanej nie zmieniają się często, więc wynik zaawansowanego dopasowywania partycji powinien pozostać taki sam przez dłuższy czas. Obliczanie go za każdym razem, gdy wykonywane jest zapytanie obejmujące te tabele, nie jest konieczne. Zamiast tego moglibyśmy zapisać zestaw pasujących partycji w jakimś katalogu i odświeżać go za każdym razem, gdy partycje się zmieniają. To trochę pracy, ale warto poświęcić czas na dopasowanie partycji dla każdego zapytania.
Nawet przy tych wszystkich ograniczeniach, to, co mamy dzisiaj, jest bardzo użytecznym rozwiązaniem, które służy większości praktycznych przypadków. Nie trzeba dodawać, że ta funkcja działa bezproblemowo z FDW join push down, poprawiając możliwości shardingu, które już posiada PostgreSQL!