sql >> Database >  >> RDS >> PostgreSQL

Verbeteringen in partitionering in PostgreSQL 11

Een partitiesysteem in PostgreSQL werd voor het eerst toegevoegd in PostgreSQL 8.1 door 2ndQuadrant-oprichter Simon Riggs . Het was gebaseerd op overerving van relaties en gebruikte een nieuwe techniek om uit te sluiten dat tabellen door een query worden gescand, de zogenaamde "beperkingsuitsluiting". Hoewel het destijds een enorme stap voorwaarts was, wordt het tegenwoordig gezien als omslachtig in gebruik en als traag, en dus aan vervanging toe.

In versie 10 werd het vervangen dankzij heroïsche inspanningen vanAmit Langote met moderne "declaratieve partitionering". Deze nieuwe technologie betekende dat je niet langer handmatig code hoefde te schrijven om tuples naar de juiste partities te routeren, en niet langer handmatig de juiste beperkingen voor elke partitie hoefde aan te geven:het systeem deed die dingen automatisch voor je.

Helaas is dat in PostgreSQL 10 vrijwel alles wat het deed. Vanwege de enorme complexiteit en de tijdsdruk waren er veel dingen in de PostgreSQL 10-implementatie die ontbraken. Robert Haas hield er een lezing over in PGConf.EU in Warschau.

Veel mensen werkten aan het verbeteren van de situatie voor PostgreSQL 11; hier is mijn poging tot een hertelling. Ik heb deze opgesplitst in drie gebieden:

  1. Nieuwe partitioneringsfuncties
  2. Betere DDL-ondersteuning voor gepartitioneerde tabellen
  3. Prestatie-optimalisaties.

Nieuwe partitioneringsfuncties

In PostgreSQL 10 kunnen uw gepartitioneerde tabellen zo zijn in RANGE en LIJST modi. Dit zijn krachtige tools om veel echte databases op te baseren, maar voor veel andere ontwerpen heb je de nieuwe modus nodig die is toegevoegd in PostgreSQL 11:HASH partitioneren . Veel klanten hebben dit nodig, en Amul Sul hard gewerkt om het mogelijk te maken. Hier is een eenvoudig voorbeeld:

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);

Het is niet verplicht om voor alle partities dezelfde moduluswaarde te gebruiken; hierdoor kunt u later meer partities maken en de rijen, indien nodig, per partitie opnieuw verdelen.

Nog een zeer handige functie, geschreven door Amit Khandekar is de mogelijkheid om UPDATE . toe te staan om rijen van de ene partitie naar de andere te verplaatsen — dat wil zeggen, als er een wijziging is in de waarden van de partitioneringskolom, wordt de rij automatisch verplaatst naar de juiste partitie. Voorheen zou die bewerking een fout hebben veroorzaakt.

Nog een nieuwe functie, geschreven door Amit Langote en met vriendelijke groeten , is dat INSERT ON CONFLICT UPDATE kan worden toegepast op gepartitioneerde tabellen . Voorheen zou dit commando mislukken als het gericht was op een gepartitioneerde tabel. Je zou het kunnen laten werken door precies te weten in welke partitie de rij terecht zou komen, maar dat is niet erg handig. Ik zal niet ingaan op de details van dat commando, maar als je ooit wenste dat je UPSERT had in Postgres, dit is het. Een waarschuwing is dat de UPDATE actie mag de rij niet naar een andere partitie verplaatsen.

Eindelijk, nog een leuke nieuwe functie in PostgreSQL 11, dit keer door Jeevan Ladhe, Beena Emerson, Ashutosh Bapat, Rahila Syed, en Robert Haas is ondersteuning voor een standaardpartitie in een gepartitioneerde tabel , dat wil zeggen, een partitie die alle rijen ontvangt die niet in een van de reguliere partities passen. Hoewel deze functie op papier mooi is, is deze functie niet erg handig voor productie-instellingen, omdat sommige bewerkingen zwaardere vergrendeling met standaardpartities vereisen dan zonder. Voorbeeld:het maken van een nieuwe partitie vereist het scannen van de standaardpartitie om te bepalen dat er geen bestaande rijen overeenkomen met de grenzen van de nieuwe partitie. Misschien zullen deze slotvereisten in de toekomst worden verlaagd, maar in de tussentijd is mijn suggestie om het niet te gebruiken.

Betere DDL-ondersteuning

In PostgreSQL 10 zouden bepaalde DDL's weigeren te werken wanneer ze worden toegepast op een gepartitioneerde tabel, en vereisen dat u elke partitie afzonderlijk verwerkt. In PostgreSQL 11 hebben we een aantal van deze beperkingen opgelost, zoals eerder aangekondigd door Simon Riggs. Ten eerste kunt u nu gebruiken CREATE INDEX op een gepartitioneerde tafel , een functie geschreven door ondergetekende. Dit kan worden gezien als gewoon een kwestie van verveling verminderen:in plaats van het commando voor elke partitie te herhalen (en ervoor te zorgen dat je het nooit vergeet voor elke nieuwe partitie), kun je het maar één keer doen voor de bovenliggende gepartitioneerde tabel, en het wordt automatisch toegepast naar alle partities, bestaande en toekomstige.

Een leuk ding om in gedachten te houden is het matchen van bestaande indexen in partities. Zoals u weet, is het maken van een index een blokkerende propositie, dus hoe minder tijd het kost, hoe beter. Ik heb deze functie geschreven zodat bestaande indexen in de partitie zouden worden vergeleken met de indexen die worden gemaakt, en als er overeenkomsten zijn, is het niet nodig om de partitie te scannen om nieuwe indexen te maken:de bestaande indexen zouden worden gebruikt.

Samen hiermee, ook door ondergetekende, kun je ook maak UNIEKE beperkingen, evenals PRIMAIRE SLEUTEL beperkingen . Twee kanttekeningen:ten eerste moet de partitiesleutel deel uitmaken van de primaire sleutel. Hierdoor kunnen de unieke controles lokaal per partitie worden uitgevoerd, waardoor globale indexen worden vermeden. Ten tweede is het nog niet mogelijk om externe sleutels te hebben die naar deze primaire sleutels verwijzen. Daar werk ik aan voor PostgreSQL 12.

Een ander ding dat je kunt doen (dankzij dezelfde persoon) is maak VOOR ELKE RIJ triggers op een gepartitioneerde tafel , en laat het van toepassing zijn op alle partities (bestaande en toekomstige). Als bijwerking kunt u uniek . hebben uitgesteld beperkingen op gepartitioneerde tabellen. Een waarschuwing:pas NA triggers zijn toegestaan, totdat we weten hoe we moeten omgaan met VOOR triggers die rijen naar een andere partitie verplaatsen.

Ten slotte kan een gepartitioneerde tabel BUITENLANDSE SLEUTEL . hebben beperkingen . Dit is erg handig om grote feitentabellen in te delen en daarbij bungelende verwijzingen te vermijden, waar iedereen een hekel aan heeft. Mijn collega Gabriele Bartolini greep me bij mijn schoot toen hij erachter kwam dat ik dit had geschreven en gepleegd, schreeuwend dat dit een game-changer was en hoe kon ik zo ongevoelig zijn om hem hiervan niet op de hoogte te stellen. Ik, ik blijf de code gewoon voor de lol hacken.

Prestatiewerk

Voorheen was het voorverwerken van query's om erachter te komen welke partities niet moesten worden gescand (uitsluiting van beperkingen) nogal simplistisch en traag. Dit is verbeterd door bewonderenswaardig teamwork van Amit Langote, David Rowley, Beena Emerson en Dilip Kumar om eerst "sneller snoeien" te introduceren en daarna "runtime snoeien" daarop gebaseerd. Het resultaat is veel krachtiger en sneller (David Rowley dit al beschreven in een vorig artikel.) Na al deze inspanning wordt partitiesnoei toegepast op drie punten in het leven van een query:

  1. Tijdens het plannen van de query,
  2. Als de queryparameters zijn ontvangen,
  3. Op elk punt waar een query-knooppunt waarden als parameters doorgeeft aan een ander knooppunt.

Dit is een opmerkelijke verbetering ten opzichte van het oorspronkelijke systeem, dat alleen kon worden toegepast tijdens het plannen van de query, en ik denk dat velen er blij mee zullen zijn.

U kunt deze functie in actie zien door EXPLAIN-uitvoer voor een query te vergelijken voor en na het uitschakelen van de enable_partition_pruning optie. Als een heel simplistisch voorbeeld, vergelijk dit plan zonder te snoeien:

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

met degene die is geproduceerd met snoeien:

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

Ik weet zeker dat je dat overtuigend zult vinden. Je kunt een heleboel meer geavanceerde voorbeelden zien door het verwachte bestand voor regressietests te lezen.

Een ander item was de introductie van partitiegewijze joins, door Ashutosh Bapat . Het idee hier is dat als je twee gepartitioneerde tabellen hebt en ze op identieke manieren zijn gepartitioneerd, wanneer ze zijn samengevoegd, je elke partitie aan de ene kant kunt samenvoegen met de bijbehorende partitie aan de andere kant; dit is veel beter dan elke partitie aan de andere kant samen te voegen met elke partitie aan de andere kant. Het feit dat de partitieschema's exact moeten overeenkomen Het lijkt misschien onwaarschijnlijk dat dit in de echte wereld veel wordt gebruikt, maar in werkelijkheid zijn er veel situaties waarin dit van toepassing is. Voorbeeld:een tabel met bestellingen en de bijbehorende tabel met bestellingen_items. Gelukkig is er al genoeg werk aan het versoepelen van deze beperking.

Het laatste item dat ik wil noemen zijn partitiegewijze aggregaten, door Jeevan Chalke, Ashutosh Bapat, en Robert Haas . Deze optimalisatie betekent dat een aggregatie die de partitiesleutels bevat in de GROUP BY clausule kan worden uitgevoerd door de rijen van elke partitie afzonderlijk te aggregeren, wat veel sneller is.

Afsluitende gedachten

Na de belangrijke ontwikkelingen in deze cyclus heeft PostgreSQL een veel boeiender verhaal over partitionering. Hoewel er nog veel verbeteringen moeten worden aangebracht, met name om de prestaties en gelijktijdigheid van verschillende bewerkingen met gepartitioneerde tabellen te verbeteren, zijn we nu op een punt beland waarop declaratieve partitionering een zeer waardevol hulpmiddel is geworden voor veel gebruiksscenario's. Bij 2ndQuadrant blijven we code bijdragen om PostgreSQL op dit en andere gebied te verbeteren, zoals we hebben gedaan voor elke afzonderlijke release sinds 8.0.


  1. Zoals Operator in Entity Framework?

  2. Een datamodel van een bureau voor de publieke opinie

  3. Hoe invoer-uitvoerparameters in SQL Server opgeslagen procedure/functie te declareren?

  4. MySQL - kan ik de maximaal toegestane tijd voor het uitvoeren van een query beperken?