Tidigare hade jag skrivit en blogg om partitionsmässigt gå med i PostgreSQL. I den bloggen hade jag pratat om en avancerad partitionsmatchningsteknik som gör att partitionsmässig koppling kan användas i fler fall. I den här bloggen kommer vi att diskutera denna teknik i detalj.
För att sammanfatta, tillåter den grundläggande partitionsmatchningstekniken att en join mellan två partitionerade tabeller kan utföras med hjälp av partitionsvis jointeknik om de två partitionerade tabellerna har exakt matchande partitionsgränser, t.ex. partitionerade tabeller prt1 och prt2 som beskrivs nedan
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)
och
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)
En join mellan prt1 och prt2 på deras partitionsnyckel (a) delas upp i joins mellan deras matchande partitioner, dvs. prt1_p1 joins prt2_p1, prt1_p2 joins prt2_p2 och prt1_p3 joins prt2_p3. Resultaten av dessa tre sammanfogningar bildar resultatet av sammanfogningen mellan prt1 och prt2. Detta har många fördelar som diskuterades i min tidigare blogg. Grundläggande partitionsmatchning kan dock inte sammanfoga två partitionerade tabeller med olika partitionsgränser. I exemplet ovan, om prt1 har en extra partition prt1_p4 FÖR VÄRDEN FRÅN (30000) TILL (50000), skulle grundläggande partitionsmatchning inte hjälpa till att konvertera en koppling mellan prt1 och prt2 till en partitionsmässig koppling eftersom de inte har exakt matchande partition gränser.
Många applikationer använder partitioner för att separera aktivt använda data och inaktuella data, en teknik som jag diskuterade i min andra blogg. Inaktuella data tas så småningom bort genom att ta bort partitioner. Nya partitioner skapas för att ta emot färska data. En join mellan två sådana partitionerade tabeller kommer oftast att använda partitionsmässig join eftersom de oftast kommer att ha matchande partitioner. Men när en aktiv partition läggs till i en av dessa tabeller eller en inaktuell partition raderas, kommer deras partitionsgränser inte att matcha förrän den andra tabellen också genomgår en liknande operation. Under det intervallet kommer en koppling mellan dessa två tabeller inte att använda partitionsmässig koppling och kan ta ovanligt längre tid att köra. Vi vill inte att en join som träffar databasen under denna korta tid ska fungera dåligt eftersom den inte kan använda partitionsmässig join. Avancerad partitionsmatchningsalgoritm hjälper till i detta och mer komplicerade fall där partitionsgränserna inte matchar exakt.
Avancerad partitionsmatchningsalgoritm
Avancerad partitionsmatchningsteknik hittar matchande partitioner från två partitionerade tabeller även när deras partitionsgränser inte matchar exakt. Den hittar matchande partitioner genom att jämföra gränserna från båda tabellerna i deras sorterade ordning liknande sammanfogningsalgoritmen. Alla två partitioner, en från var och en av de partitionerade tabellen, vars gränser matchar exakt eller överlappar anses vara sammanfogningspartner eftersom de kan innehålla sammanfogningsrader. Om vi fortsätter med exemplet ovan, låt oss säga att en aktiv ny partition prt2_p4 läggs till prt4. De partitionerade tabellerna ser nu ut så här:
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)
och
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)
Det är lätt att se att partitionsgränserna för prt1_p1 och prt2_p1, prt1_p2 och prt2_p2 och prt1_p3 och prt2_p3 matchar. Men till skillnad från grundläggande partitionsmatchning kommer avancerad partitionsmatchning att veta att prt2_p4 inte har någon matchande partition i prt1. Om kopplingen mellan prt1 och prt2 är en INNER-koppling eller när prt2 är INNER-relation i kopplingen, kommer kopplingsresultatet inte att ha någon rad från prt2_p4. Aktiverad med detaljerad information om matchande partitioner och partitioner som inte matchar, i motsats till bara om partitionsgränserna matchar eller inte, kan frågeoptimeraren bestämma om den ska använda partitionsvis join eller inte. I det här fallet kommer den att välja att utföra kopplingen som join mellan de matchande partitionerna och lämna prt2_p4 åt sidan. Men det är inte mycket som en "avancerad" partitionsmatchning. Låt oss se lite mer komplicerade fall med listpartitionerade tabeller den här gången:
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')
och
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')
Observera att det finns exakt tre partitioner i båda relationerna men partitionsvärdeslistorna skiljer sig åt. Listan som motsvarar partitionen plt1_p2 matchar exakt den för plt2_p2. Förutom det har inga två partitioner, en från vardera sidan, exakt matchande listor. Avancerad partitionsmatchningsalgoritm härleder att plt1_p1 och plt2_p1 har överlappande listor och deras listor överlappar inte med någon annan partition från den andra relationen. På samma sätt för plt1_p3 och plt2_p3. Frågeoptimeraren ser sedan att kopplingen mellan plt1 och plt2 kan utföras som partitionsmässig koppling genom att sammanfoga de matchande partitionerna, dvs plt1_p1 och plt2_p1, plt1_p2 och plt2_p2, respektive plt1_p3 och plt2_p3. Algoritmen kan hitta matchande partitioner i ännu mer komplexa partitionsbundna uppsättningar av list samt intervallpartitionerade tabeller. Men vi kommer inte att täcka dem för korthetens skull. Intresserade och mer vågade läsare kan ta en titt på commit. Den har också många testfall, som visar olika scenarier där avancerad partitionsmatchningsalgoritm används.
Begränsningar
Yttre sammanfogningar med matchande partitioner saknas på insidan
Outer joins utgör ett särskilt problem i PostgreSQL-världen. Betrakta prt2 LEFT JOIN prt1, i exemplet ovan, där prt2 är en YTTRE relation. prt2_p4 har ingen kopplingspartner i prt1 och ändå bör raderna i den partitionen vara en del av kopplingsresultatet eftersom de tillhör den yttre relationen. I PostgreSQL när den INRE sidan av en koppling är tom, representeras den av en "dummy"-relation som inte avger några rader men som fortfarande känner till schemat för den relationen. Vanligtvis uppstår en "dummy"-relation från en icke-dummy-relation som inte kommer att avge några rader på grund av viss frågeoptimering som exkludering av begränsningar. PostgreSQL:s frågeoptimerare markerar en sådan icke-dummy-relation som dummy och executorn fortsätter normalt när en sådan koppling utförs. Men när det inte finns någon matchande inre partition för en yttre partition, finns det ingen "befintlig enhet" som kan markeras som "dummy". Till exempel, i det här fallet finns det ingen prt1_p4 som kan representera en dummy inre partition som förenar yttre prt2_p4. Just nu har PostgreSQL inte ett sätt att "skapa" sådana "dummy"-relationer under planeringen. Därför använder frågeoptimeraren inte partitionsvis join i detta fall.
Helst kräver en sådan sammanfogning med tom inre bara ett schema över den inre relationen och inte en hel relation. Detta schema kan härledas från den partitionerade tabellen själv. Allt den behöver är en förmåga att producera sammanfogningsraden genom att använda kolumnerna från en rad på den yttre sidan sammanfogade av NULL-värden för kolumnerna från insidan. När vi väl har den förmågan i PostgreSQL kommer frågeoptimeraren att kunna använda partitionsmässig join även i dessa fall.
Låt mig betona att de yttre sammanfogningarna där det inte saknas partitioner på den inre sammanfogningen använder partitionsmässig sammanfogning.
Flera matchande partitioner
När tabellerna är uppdelade så att flera partitioner från ena sidan matchar en eller flera partitioner på den andra sidan, kan partitionsmässig koppling inte användas eftersom det inte finns något sätt att inducera en "Lägg till"-relation under planeringstiden som representerar två eller flera skiljeväggar tillsammans. Förhoppningsvis kommer vi också att ta bort den begränsningen någon gång och tillåta partitionsmässig koppling att användas även i dessa fall.
Hash-partitionerade tabeller
Partitionsgränserna för två hash-partitionerade tabeller som använder samma modulo matchar alltid. När modulo är annorlunda kan en rad från en given partition i en tabell ha sina sammanfogningspartners i många av partitionerna i den andra, sålunda matchar en given partition från ena sidan flera partitioner av den andra tabellen, vilket gör partitionsmässig sammanfogning ineffektiv.
När den avancerade partitionsmatchningsalgoritmen inte lyckas hitta matchande partitioner eller partitionsmässig koppling inte kan användas på grund av ovanstående begränsningar, faller PostgreSQL tillbaka för att ansluta de partitionerade tabellerna som vanliga tabeller.
Avancerad partitionsmatchningstid
Simon tog upp en intressant punkt när han kommenterade funktionen. Partitioner i en partitionerad tabell ändras inte ofta så resultatet av avancerad partitionsmatchning bör förbli detsamma under längre tid. Att beräkna det varje gång en fråga som involverar dessa tabeller körs är onödigt. Istället kunde vi spara uppsättningen av matchande partitioner i någon katalog och uppdatera den varje gång partitionerna ändras. Det är en del arbete men det är värt den tid som läggs ner på att matcha partitionen för varje fråga.
Även med alla dessa begränsningar är det vi har idag en mycket användbar lösning som tjänar de flesta praktiska fallen. Onödigt att säga att den här funktionen fungerar sömlöst med FDW join push down vilket förbättrar skärningskapaciteten som PostgreSQL redan har!