Ett partitioneringssystem i PostgreSQL lades först till i PostgreSQL 8.1 av 2ndQuadrants grundare Simon Riggs . Den baserades på relationsarv och använde en ny teknik för att utesluta tabeller från att skannas av en fråga, kallad "begränsningsuteslutning". Även om det var ett stort steg framåt på den tiden, ses det numera som både besvärligt att använda och långsamt och behöver därför bytas ut.
I version 10 ersattes den tack vare heroiska ansträngningar av Amit Langote med "deklarativ partitionering" i modern stil. Den här nya tekniken innebar att du inte längre behövde skriva kod manuellt för att dirigera tupler till deras korrekta partitioner, och inte längre behövde manuellt deklarera korrekta begränsningar för varje partition:systemet gjorde dessa saker automatiskt åt dig.
Tyvärr, i PostgreSQL 10 är det i stort sett allt det gjorde. På grund av den rena komplexiteten och tidsbegränsningarna var det många saker i PostgreSQL 10-implementeringen som saknades. Robert Haas höll ett föredrag om det i Warszawas PGConf.EU.
Många människor arbetade med att förbättra situationen för PostgreSQL 11; här är mitt försök till omräkning. Jag delar upp dessa i tre områden:
- Nya partitioneringsfunktioner
- Bättre DDL-stöd för partitionerade tabeller
- Prestandaoptimeringar.
Nya partitioneringsfunktioner
I PostgreSQL 10 kan dina partitionerade tabeller vara så i RANGE och LISTA lägen. Dessa är kraftfulla verktyg att basera många verkliga databaser på, men för många andra konstruktioner behöver du det nya läget som lagts till i PostgreSQL 11:HASH partitionering . Många kunder behöver detta, och Amul Sul arbetat hårt för att göra det möjligt. Här är ett enkelt exempel:
CREATE TABLE clients ( client_id INTEGER, name TEXT ) PARTITION BY HASH (client_id); CREATE TABLE clients_0 PARTITION OF clients FOR VALUES WITH (MODULUS 3, REMAINDER 0); CREATE TABLE clients_1 PARTITION OF clients FOR VALUES WITH (MODULUS 3, REMAINDER 1); CREATE TABLE clients_2 PARTITION OF clients FOR VALUES WITH (MODULUS 3, REMAINDER 2);
Det är inte obligatoriskt att använda samma modulvärde för alla partitioner; detta låter dig skapa fler partitioner senare och omfördela raderna en partition i taget, om det behövs.
En annan mycket användbar funktion, skriven av Amit Khandekar är möjligheten att tillåta UPPDATERING för att flytta rader från en partition till en annan — det vill säga, om det finns en förändring i värdena för partitioneringskolumnen, flyttas raden automatiskt till rätt partition. Tidigare skulle den operationen ha orsakat ett fel.
En annan ny funktion, skriven av Amit Langote och med vänliga hälsningar , är att INSERT ON CONFLICT UPDATE kan tillämpas på partitionerade tabeller . Tidigare skulle detta kommando misslyckas om det riktade sig mot en partitionerad tabell. Du kan få det att fungera genom att veta exakt vilken partition raden skulle hamna i, men det är inte särskilt bekvämt. Jag kommer inte gå igenom detaljerna för det kommandot, men om du någonsin har önskat att du hade UPSERT i Postgres, det här är det. En varning är att UPPDATERING action kanske inte flyttar raden till en annan partition.
Äntligen en annan söt ny funktion i PostgreSQL 11, denna gång av Jeevan Ladhe, Beena Emerson, Ashutosh Bapat, Rahila Syed, och Robert Haas är stöd för en standardpartition i en partitionerad tabell , det vill säga en partition som tar emot alla rader som inte passar i någon av de vanliga partitionerna. Men även om den är bra på papper, är den här funktionen inte särskilt bekväm i produktionsinställningar eftersom vissa operationer kräver tyngre låsning med standardpartitioner än utan. Exempel:att skapa en ny partition kräver att standardpartitionen skannas för att fastställa att inga befintliga rader matchar den nya partitionens gränser. Kanske kommer dessa låskrav att sänkas i framtiden, men under tiden är mitt förslag att inte använda det.
Bättre DDL-stöd
I PostgreSQL 10 skulle vissa DDL vägra att fungera när de applicerades på en partitionerad tabell och krävde att du bearbetade varje partition individuellt. I PostgreSQL 11 har vi fixat några av dessa begränsningar, som tidigare meddelats av Simon Riggs. Först kan du nu använda SKAPA INDEX på ett partitionerat bord , en funktion skriven av yours truly. Den här kan ses som bara en fråga om att minska tröttheten:istället för att upprepa kommandot för varje partition (och se till att aldrig glömma för varje ny partition), kan du bara göra det en gång för den överordnade partitionerade tabellen, och det gäller automatiskt till alla partitioner, befintliga och framtida.
En cool sak att tänka på är matchningen av befintliga index i partitioner. Som du vet är att skapa ett index ett blockerande förslag, så ju mindre tid det tar, desto bättre. Jag skrev den här funktionen så att befintliga index i partitionen skulle jämföras med indexen som skapas, och om det finns matchningar är det inte nödvändigt att skanna partitionen för att skapa nya index:de befintliga indexen skulle användas.
Tillsammans med detta, även av dig, kan du också skapa UNIKA begränsningar, såväl som PRIMÄRNYCKEL begränsningar . Två varningar:För det första måste partitionsnyckeln vara en del av primärnyckeln. Detta gör att de unika kontrollerna kan göras lokalt per partition, och undviker globala index. För det andra är det inte möjligt att ha främmande nycklar som refererar till dessa primärnycklar ännu. Jag jobbar på det för PostgreSQL 12.
En annan sak du kan göra (tack vare samma person) är att skapa FÖR VARJE RAD triggers på en partitionerad tabell , och få det att gälla för alla partitioner (befintliga och framtida). Som en bieffekt kan du ha skjutit upp unik begränsningar på partitionerade tabeller. En varning:endast EFTER utlösare är tillåtna tills vi kommer på hur vi ska hantera INNAN triggers som flyttar rader till en annan partition.
Slutligen, en partitionerad tabell kan ha FREIGN KEY begränsningar . Detta är väldigt praktiskt för att dela upp stora faktatabeller samtidigt som man undviker dinglande referenser, som alla avskyr. Min kollega Gabriele Bartolini tog mig i mitt knä när han fick reda på att jag hade skrivit och begått detta, och skrek att detta var en gamechanger och hur kunde jag vara så okänslig att inte informera honom om detta. Jag, jag fortsätter bara att hacka koden för skojs skull.
Prestandaarbete
Tidigare var förbearbetning av frågor för att ta reda på vilka partitioner som inte skulle skannas (uteslutning av begränsningar) ganska förenklat och långsamt. Detta har förbättrats av beundransvärt lagarbete som utförts av Amit Langote, David Rowley, Beena Emerson, Dilip Kumar för att introducera "snabbare beskärning" först och "runtime pruning" baserat på det efteråt. Resultatet är mycket kraftfullare och snabbare (David Rowley har redan beskrivit detta i en tidigare artikel.) Efter alla dessa ansträngningar tillämpas partitionsbeskärning vid tre punkter i en frågas livstid:
- Vid frågeplanens tid,
- När frågeparametrarna tas emot,
- Vid varje punkt där en frågenod skickar värden som parametrar till en annan nod.
Detta är en anmärkningsvärd förbättring från det ursprungliga systemet som bara kunde tillämpas vid frågeplan, och jag tror att det kommer att glädja många.
Du kan se den här funktionen i funktion genom att jämföra EXPLAIN-utdata för en fråga före och efter att enable_partition_pruning stängts av alternativ. Som ett mycket förenklat exempel, jämför denna plan utan beskärning:
SET enable_partition_pruning TO off; EXPLAIN (ANALYZE, COSTS off) SELECT * FROM clientes WHERE cliente_id = 1234;
QUERY PLAN ------------------------------------------------------------------------- Append (actual time=6.658..10.549 rows=1 loops=1) -> Seq Scan on clientes_1 (actual time=4.724..4.724 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 24978 -> Seq Scan on clientes_00 (actual time=1.914..1.914 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12644 -> Seq Scan on clientes_2 (actual time=0.017..1.021 rows=1 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12570 -> Seq Scan on clientes_3 (actual time=0.746..0.746 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12448 -> Seq Scan on clientes_01 (actual time=0.648..0.648 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12482 -> Seq Scan on clientes_4 (actual time=0.774..0.774 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12400 -> Seq Scan on clientes_5 (actual time=0.717..0.717 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12477 Planning Time: 0.375 ms Execution Time: 10.603 ms
med den som produceras med beskärning:
EXPLAIN (ANALYZE, COSTS off) SELECT * FROM clientes WHERE cliente_id = 1234;
QUERY PLAN ---------------------------------------------------------------------- Append (actual time=0.054..2.787 rows=1 loops=1) -> Seq Scan on clientes_2 (actual time=0.052..2.785 rows=1 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12570 Planning Time: 0.292 ms Execution Time: 2.822 ms
Jag är säker på att du kommer att tycka att det är övertygande. Du kan se massor av mer sofistikerade exempel genom att läsa den förväntade filen för regressionstest.
En annan punkt var introduktionen av partitionsvisa sammanfogningar, av Ashutosh Bapat . Tanken här är att om du har två partitionerade tabeller, och de är partitionerade på identiska sätt, då när de är sammanfogade kan du ansluta varje partition på ena sidan till dess matchande partition på den andra sidan; detta är mycket bättre än att sammanfoga varje partition på sidan till varje partition på den andra sidan. Det faktum att partitionsscheman måste matcha exakt kan göra att detta verkar osannolikt att ha mycket användning i verkligheten, men i verkligheten finns det många situationer där detta gäller. Exempel:en ordertabell och dess motsvarande orders_items-tabell. Tack och lov finns det redan mycket arbete med att lindra denna begränsning.
Det sista jag vill nämna är partitionsmässiga aggregat, av Jeevan Chalke, Ashutosh Bapat, och Robert Haas . Denna optimering innebär att en aggregering som inkluderar partitionsnycklarna i GROUP BY sats kan exekveras genom att aggregera varje partitions rader separat, vilket är mycket snabbare.
Avslutande tankar
Efter den betydande utvecklingen i den här cykeln har PostgreSQL en mycket mer övertygande partitioneringshistoria. Även om det fortfarande finns många förbättringar att göra, särskilt för att förbättra prestandan och samtidigheten av olika operationer som involverar partitionerade tabeller, är vi nu vid en punkt där deklarativ partitionering har blivit ett mycket värdefullt verktyg för många användningsfall. På 2ndQuadrant kommer vi att fortsätta att bidra med kod för att förbättra PostgreSQL inom detta och andra områden, som vi har gjort för varje enskild version sedan 8.0.