Eerder had ik een blog geschreven over partitiegewijs meedoen in PostgreSQL. In die blog had ik het over een geavanceerde techniek voor het matchen van partities waarmee partitiegewijze join in meer gevallen kan worden gebruikt. In deze blog gaan we uitgebreid in op deze techniek.
Samenvattend:de basistechniek voor het matchen van partities maakt het mogelijk om een join tussen twee gepartitioneerde tabellen uit te voeren met behulp van partitiegewijze join-techniek als de twee gepartitioneerde tabellen exact overeenkomende partitiegrenzen hebben, b.v. gepartitioneerde tabellen prt1 en prt2 hieronder beschreven
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)
en
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)
Een join tussen prt1 en prt2 op hun partitiesleutel (a) wordt opgesplitst in joins tussen hun overeenkomende partities, d.w.z. prt1_p1 voegt zich bij prt2_p1, prt1_p2 voegt zich bij prt2_p2 en prt1_p3 voegt zich bij prt2_p3. De resultaten van deze drie joins vormen samen het resultaat van de join tussen prt1 en prt2. Dit heeft veel voordelen zoals besproken in mijn vorige blog. Standaard partitie-overeenkomsten kunnen echter niet twee gepartitioneerde tabellen met verschillende partitiegrenzen samenvoegen. Als in het bovenstaande voorbeeld prt1 een extra partitie heeft prt1_p4 FOR VALUES FROM (30000) TO (50000), zou standaard partitie-matching niet helpen om een join tussen prt1 en prt2 om te zetten in een partitiegewijze join, omdat ze niet exact overeenkomende partitie hebben grenzen.
Veel toepassingen gebruiken partities om actief gebruikte gegevens en verouderde gegevens te scheiden, een techniek die ik in mijn andere blog heb besproken. De verouderde gegevens worden uiteindelijk verwijderd door partities te verwijderen. Er worden nieuwe partities gemaakt om nieuwe gegevens op te nemen. Een join tussen twee van dergelijke gepartitioneerde tabellen zal meestal partitiegewijze join gebruiken, omdat ze meestal overeenkomende partities hebben. Maar wanneer een actieve partitie aan een van deze tabellen wordt toegevoegd of een verouderde partitie wordt verwijderd, komen hun partitiegrenzen niet overeen totdat de andere tabel ook een vergelijkbare bewerking ondergaat. Tijdens dat interval zal een join tussen deze twee tabellen geen partitie-gewijze join gebruiken en kan het ongebruikelijk langer duren om uit te voeren. We willen niet dat een join die gedurende deze korte tijd de database raakt, slecht presteert, omdat deze geen partitie-gewijze join kan gebruiken. Het geavanceerde algoritme voor het matchen van partities helpt in deze en meer gecompliceerde gevallen waarin partitiegrenzen niet exact overeenkomen.
Geavanceerd algoritme voor het matchen van partities
Geavanceerde partitie-afstemmingstechniek vindt overeenkomende partities van twee gepartitioneerde tabellen, zelfs als hun partitiegrenzen niet exact overeenkomen. Het vindt overeenkomende partities door de grenzen van beide tabellen te vergelijken in hun gesorteerde volgorde, vergelijkbaar met het merge join-algoritme. Elke twee partities, één van elk van de gepartitioneerde tabellen, waarvan de grenzen exact overeenkomen of overlappen, worden beschouwd als samenvoegende partners, aangezien ze samenvoegende rijen kunnen bevatten. Laten we doorgaan met het bovenstaande voorbeeld, laten we zeggen dat een actieve nieuwe partitie prt2_p4 wordt toegevoegd aan prt4. De gepartitioneerde tabellen zien er nu als volgt uit:
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)
en
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)
Het is gemakkelijk te zien dat de partitiegrenzen van prt1_p1 en prt2_p1, prt1_p2 en prt2_p2, en prt1_p3 en prt2_p3 respectievelijk overeenkomen. Maar in tegenstelling tot standaard partitie-matching, weet geavanceerde partitie-matching dat prt2_p4 geen overeenkomende partitie heeft in prt1. Als de join tussen prt1 en prt2 een INNER-join is of als prt2 een BINNEN-relatie is in de join, zal het resultaat van de samenvoeging geen rij uit prt2_p4 hebben. Ingeschakeld met gedetailleerde informatie over de overeenkomende partities en partities die niet overeenkomen, in plaats van alleen of de partitiegrenzen overeenkomen of niet, kan query-optimalisatie beslissen of partitiegewijze join moet worden gebruikt of niet. In dit geval zal het ervoor kiezen om de join uit te voeren als een join tussen de overeenkomende partities, waarbij prt2_p4 opzij wordt gelaten. Maar dat lijkt niet veel op een "geavanceerde" partitie-matching. Laten we deze keer een wat ingewikkelder geval bekijken met behulp van gepartitioneerde tabellen:
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')
en
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')
Merk op dat er precies drie partities zijn in beide relaties, maar de partitiewaardelijsten verschillen. De lijst die overeenkomt met partitie plt1_p2 komt exact overeen met die van plt2_p2. Afgezien daarvan hebben geen twee partities, één van elke kant, exact overeenkomende lijsten. Het geavanceerde algoritme voor het matchen van partities leidt af dat plt1_p1 en plt2_p1 overlappende lijsten hebben en dat hun lijsten niet overlappen met een andere partitie uit de andere relatie. Hetzelfde geldt voor plt1_p3 en plt2_p3. Query-optimizer ziet dan dat de join tussen plt1 en plt2 kan worden uitgevoerd als partitiegewijze join door de overeenkomende partities, d.w.z. respectievelijk plt1_p1 en plt2_p1, plt1_p2 en plt2_p2, en plt1_p3 en plt2_p3 samen te voegen. Het algoritme kan overeenkomende partities vinden in nog complexere partitiegebonden sets van lijsten en in bereikgepartitioneerde tabellen. Maar kortheidshalve zullen we ze niet behandelen. Geïnteresseerde en meer gedurfde lezers kunnen een kijkje nemen in de commit. Het heeft ook veel testcases, die verschillende scenario's laten zien waarin een geavanceerd algoritme voor het matchen van partities wordt gebruikt.
Beperkingen
Buitenste joins met bijpassende partities ontbreken aan de binnenkant
Outer joins vormen een bijzonder probleem in de PostgreSQL-wereld. Overweeg prt2 LEFT JOIN prt1, in het bovenstaande voorbeeld, waar prt2 een OUTER-relatie is. prt2_p4 heeft geen join-partner in prt1 en toch moeten de rijen in die partitie deel uitmaken van het join-resultaat, omdat ze tot de buitenste relatie behoren. Wanneer in PostgreSQL de BINNENKANT van een join leeg is, wordt deze weergegeven door een "dummy"-relatie die geen rijen uitzendt maar toch het schema van die relatie kent. Gewoonlijk komt een "dummy"-relatie voort uit een niet-dummy-relatie die geen rijen zal uitzenden vanwege een of andere query-optimalisatie, zoals het uitsluiten van beperkingen. De query-optimizer van PostgreSQL markeert zo'n niet-dummy-relatie als dummy en de uitvoerder gaat normaal verder bij het uitvoeren van een dergelijke join. Maar als er geen overeenkomende binnenpartitie voor een buitenste partitie is, is er geen "bestaande entiteit" die als "dummy" kan worden gemarkeerd. In dit geval is er bijvoorbeeld geen prt1_p4 die een dummy binnenpartitie kan vertegenwoordigen die de buitenste prt2_p4 verbindt. Op dit moment heeft PostgreSQL geen manier om dergelijke "dummy" relaties te "creëren" tijdens de planning. Daarom gebruikt de query-optimizer in dit geval geen partitiegewijze join.
Idealiter vereist zo'n join met lege inner alleen een schema van de innerlijke relatie en niet een hele relatie. Dit schema kan worden afgeleid uit de gepartitioneerde tabel zelf. Het enige dat nodig is, is de mogelijkheid om de join-rij te produceren door de kolommen van een rij aan de buitenkant te gebruiken, verbonden door NULL-waarden voor de kolommen van de binnenkant. Zodra we die mogelijkheid hebben in PostgreSQL, kan query-optimalisatie zelfs in deze gevallen partitiegewijs deelnemen.
Laat me benadrukken dat de outer joins waar er geen ontbrekende partities op de inner join zijn, wel partitie-gewijze join gebruiken.
Meerdere overeenkomende partities
Wanneer de tabellen zodanig zijn gepartitioneerd dat meerdere partities van de ene kant overeenkomen met een of meer partities aan de andere kant, kan partitiegewijze join niet worden gebruikt, omdat er geen manier is om een "Toevoegen"-relatie te induceren tijdens de planningstijd die twee of meer vertegenwoordigt partities bij elkaar. Hopelijk zullen we die beperking ooit ook verwijderen en toestaan dat partitiegewijze join ook in die gevallen wordt gebruikt.
Hash gepartitioneerde tabellen
Partitiegrenzen van twee hash-gepartitioneerde tabellen die dezelfde modulo gebruiken, komen altijd overeen. Wanneer de modulo anders is, kan een rij van een bepaalde partitie van de ene tabel zijn deelnemende partners hebben in veel van de partities van de andere, dus een bepaalde partitie van de ene kant komt overeen met meerdere partities van de andere tafel, waardoor partitiegewijze join ondoeltreffend wordt.
Wanneer het geavanceerde algoritme voor het matchen van partities geen overeenkomende partities kan vinden of partitiegewijze join niet kan worden gebruikt vanwege de bovenstaande beperkingen, valt PostgreSQL terug om de gepartitioneerde tabellen als gewone tabellen samen te voegen.
Geavanceerde partitie-afstemmingstijd
Simon bracht een interessant punt naar voren toen hij commentaar gaf op de functie. Partities van een gepartitioneerde tabel veranderen niet vaak, dus het resultaat van geavanceerde partitie-matching zou voor langere tijd hetzelfde moeten blijven. Het is niet nodig om het elke keer te berekenen wanneer een query met deze tabellen wordt uitgevoerd. In plaats daarvan zouden we de set overeenkomende partities in een of andere catalogus kunnen opslaan en deze elke keer dat de partities veranderen, vernieuwen. Dat is wat werk, maar het is de tijd waard die besteed wordt aan het matchen van de partitie voor elke zoekopdracht.
Zelfs met al deze beperkingen, is wat we vandaag hebben een zeer nuttige oplossing die de meeste praktische gevallen dient. Onnodig te zeggen dat deze functie naadloos samenwerkt met FDW join push down, waardoor de sharding-mogelijkheden die PostgreSQL al heeft, worden verbeterd!