Tidligere havde jeg skrevet en blog om partitionsmæssig deltagelse i PostgreSQL. I den blog havde jeg talt om en avanceret partitionsmatchningsteknik, som gør det muligt at bruge partitionsmæssig joinforbindelse i flere tilfælde. I denne blog vil vi diskutere denne teknik i detaljer.
For at opsummere, tillader den grundlæggende partitionsmatchningsteknik at udføre en joinforbindelse mellem to partitionerede tabeller ved hjælp af partitionsmæssig jointeknik, hvis de to partitionerede tabeller har nøjagtigt matchende partitionsgrænser, f.eks. opdelte tabeller prt1 og prt2 beskrevet nedenfor
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)
og
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 joinforbindelse mellem prt1 og prt2 på deres partitionsnøgle (a) opdeles i joins mellem deres matchende partitioner, dvs. prt1_p1 joins prt2_p1, prt1_p2 joins prt2_p2 og prt1_p3 joins prt2_p3. Resultaterne af disse tre sammenføjninger danner sammen resultatet af sammenføjningen mellem prt1 og prt2. Dette har mange fordele som diskuteret i min tidligere blog. Grundlæggende partitionsmatchning kan dog ikke forbinde to partitionerede tabeller med forskellige partitionsgrænser. I ovenstående eksempel, hvis prt1 har en ekstra partition prt1_p4 FOR VÆRDIER FRA (30000) TIL (50000), ville grundlæggende partitionsmatchning ikke hjælpe med at konvertere en joinforbindelse mellem prt1 og prt2 til en partitionsmæssig joinforbindelse, da de ikke har nøjagtig matchende partition grænser.
Mange applikationer bruger partitioner til at adskille aktivt brugte data og forældede data, en teknik jeg diskuterede i min anden blog. De forældede data fjernes til sidst ved at droppe partitioner. Nye partitioner oprettes for at rumme friske data. En joinforbindelse mellem to sådanne partitionerede tabeller vil for det meste bruge partitionsmæssig join, da de det meste af tiden vil have matchende partitioner. Men når en aktiv partition bliver tilføjet til en af disse tabeller, eller en forældet en bliver slettet, vil deres partitionsgrænser ikke matche, før den anden tabel også gennemgår en lignende operation. I det interval vil en joinforbindelse mellem disse to tabeller ikke bruge partitionsmæssig joinforbindelse og kan tage usædvanlig længere tid at udføre. Vi ønsker ikke, at en joinforbindelse, der rammer databasen i denne korte varighed, skal fungere dårligt, da den ikke kan bruge partitionsmæssig join. Avanceret partitionsmatchningsalgoritme hjælper i dette og mere komplicerede tilfælde, hvor partitionsgrænserne ikke matcher nøjagtigt.
Avanceret partitionsmatchningsalgoritme
Avanceret partitionsmatchningsteknik finder matchende partitioner fra to partitionerede tabeller, selv når deres partitionsgrænser ikke matcher nøjagtigt. Den finder matchende partitioner ved at sammenligne grænserne fra begge tabeller i deres sorterede rækkefølge svarende til flettesammenføjningsalgoritmen. Enhver to partitioner, en fra hver af de opdelte tabeller, hvis grænser matcher nøjagtigt eller overlapper, anses for at være sammenføjningspartnere, da de kan indeholde sammenføjningsrækker. Fortsætter med ovenstående eksempel, lad os sige, at en aktiv ny partition prt2_p4 bliver tilføjet til prt4. De opdelte tabeller ser nu sådan ud:
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)
og
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 er let at se, at partitionsgrænserne for prt1_p1 og prt2_p1, prt1_p2 og prt2_p2 og prt1_p3 og prt2_p3 henholdsvis matcher. Men i modsætning til grundlæggende partitionsmatching vil avanceret partitionsmatchning vide, at prt2_p4 ikke har nogen matchende partition i prt1. Hvis joinforbindelsen mellem prt1 og prt2 er en INNER join, eller når prt2 er INNER relation i joinningen, vil joinresultatet ikke have nogen række fra prt2_p4. Aktiveret med detaljerede oplysninger om de matchende partitioner og partitioner, som ikke matcher, i modsætning til blot om partitionsgrænser matcher eller ej, kan query optimizer beslutte, om der skal bruges partitionsmæssig joinforbindelse eller ej. I dette tilfælde vil den vælge at udføre joinforbindelsen som join mellem de matchende partitioner og lade prt2_p4 ligge til side. Men det er ikke meget som en "avanceret" partitionsmatchning. Lad os se en lidt mere kompliceret sag ved at bruge listeopdelte tabeller denne gang:
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')
og
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')
Bemærk, at der er nøjagtig tre partitioner i begge relationer, men partitionsværdilister er forskellige. Listen, der svarer til partitionen plt1_p2, matcher nøjagtigt den for plt2_p2. Bortset fra det har ingen to partitioner, én fra hver side, nøjagtigt matchende lister. Avanceret partitionsmatchningsalgoritme udleder, at plt1_p1 og plt2_p1 har overlappende lister, og deres lister overlapper ikke nogen anden partition fra den anden relation. Tilsvarende for plt1_p3 og plt2_p3. Forespørgselsoptimering ser derefter, at sammenkædningen mellem plt1 og plt2 kan udføres som partitionsmæssig joinforbindelse ved at forbinde de matchende partitioner, dvs. Algoritmen kan finde matchende partitioner i endnu mere komplekse partitionsbundne listesæt såvel som områdepartitionerede tabeller. Men vi vil ikke dække dem for korthedens skyld. Interesserede og mere vovede læsere kan tage et kig på commit. Det har også mange testcases, som viser forskellige scenarier, hvor avanceret partitionsmatchningsalgoritme bruges.
Begrænsninger
Ydre sammenføjninger med matchende skillevægge mangler på indersiden
Ydre sammenføjninger udgør et særligt problem i PostgreSQL-verdenen. Overvej prt2 LEFT JOIN prt1 i ovenstående eksempel, hvor prt2 er en YDRE relation. prt2_p4 har ikke en sammenføjningspartner i prt1, og alligevel bør rækkerne i den partition være en del af sammenføjningsresultatet, da de tilhører den ydre relation. I PostgreSQL, når den INDERSTE side af en joinforbindelse er tom, er den repræsenteret af en "dummy"-relation, som ikke udsender rækker, men som stadig kender skemaet for denne relation. Normalt opstår en "dummy"-relation fra en ikke-dummy-relation, som ikke kommer til at udsende nogen rækker på grund af en vis forespørgselsoptimering, såsom begrænsningsekskludering. PostgreSQL's forespørgselsoptimering markerer en sådan ikke-dummy-relation som dummy, og eksekveren fortsætter normalt, når den udfører en sådan joinforbindelse. Men når der ikke er nogen matchende indre partition for en ydre partition, er der ingen "eksisterende entitet", som kan markeres som "dummy". For eksempel er der i dette tilfælde ingen prt1_p4, som kan repræsentere dummy indre partition, der forbinder ydre prt2_p4. Lige nu har PostgreSQL ikke en måde at "skabe" sådanne "dummy"-relationer under planlægningen. Derfor bruger forespørgselsoptimeringsværktøjet ikke partitionsmæssig joinforbindelse i dette tilfælde.
Ideelt set kræver sådan en sammenføjning med tom indre kun et skema af den indre relation og ikke en hel relation. Dette skema kan udledes af selve den partitionerede tabel. Det eneste, det behøver, er en evne til at producere sammenføjningsrækken ved at bruge kolonnerne fra en række på ydersiden, der er forbundet med NULL-værdier for kolonnerne fra indersiden. Når vi først har denne evne i PostgreSQL, vil forespørgselsoptimering være i stand til at bruge partitionsmæssig joinforbindelse selv i disse tilfælde.
Lad mig understrege, at de ydre sammenføjninger, hvor der ikke mangler skillevægge på den indre sammenføjning, bruger partitionsmæssig sammenføjning.
Flere matchende partitioner
Når tabellerne er opdelt således, at flere partitioner fra den ene side matcher en eller flere partitioner på den anden side, kan partition-wise join ikke bruges, da der ikke er nogen måde at inducere en "Append"-relation under planlægningstiden, som repræsenterer to eller flere skillevægge sammen. Forhåbentlig vil vi også fjerne den begrænsning engang og tillade, at partitionsmæssig joinforbindelse også bruges i disse tilfælde.
Hash-partitionerede tabeller
Partitionsgrænser for to hash-partitionerede tabeller, der bruger samme modulo, matcher altid. Når modulo er forskellig, kan en række fra en given partition i den ene tabel have sine sammenføjningspartnere i mange af partitionerne i den anden, således matcher en given partition fra den ene side flere partitioner af den anden tabel, hvilket gør partitionsmæssig joinforbindelse ineffektiv.
Når den avancerede partitionsmatchningsalgoritme ikke kan finde matchende partitioner eller partitionsmæssig join ikke kan bruges på grund af ovenstående begrænsninger, falder PostgreSQL tilbage for at forbinde de partitionerede tabeller som almindelige tabeller.
Avanceret partitionsmatchningstid
Simon kom med en interessant pointe, da han kommenterede funktionen. Partitioner i en partitioneret tabel ændres ikke ofte, så resultatet af avanceret partitionsmatchning bør forblive det samme i længere tid. Det er unødvendigt at beregne det, hver gang en forespørgsel, der involverer disse tabeller, udføres. I stedet kunne vi gemme sættet af matchende partitioner i et eller andet katalog og opdatere det hver gang partitionerne ændres. Det er noget arbejde, men det er værd at bruge tiden på at matche partitionen for hver forespørgsel.
Selv med alle disse begrænsninger er det, vi har i dag, en meget nyttig løsning, som tjener de fleste praktiske sager. Det er overflødigt at sige, at denne funktion fungerer problemfrit med FDW join push-down, hvilket forbedrer de sharding-funktioner, som PostgreSQL allerede har!