sql >> Database >  >> RDS >> PostgreSQL

Een gids voor het partitioneren van gegevens in PostgreSQL

Wat is gegevenspartitionering?

Voor databases met extreem grote tabellen is partitionering een geweldige en slimme truc voor databaseontwerpers om de databaseprestaties te verbeteren en het onderhoud veel gemakkelijker te maken. De maximale tabelgrootte die is toegestaan ​​in een PostgreSQL-database is 32 TB, maar tenzij deze draait op een nog niet uitgevonden computer uit de toekomst, kunnen prestatieproblemen optreden op een tafel met slechts een honderdste van die ruimte.

Partitioneren splitst een tabel in meerdere tabellen, en wordt over het algemeen zo gedaan dat toepassingen die toegang hebben tot de tabel geen verschil merken, behalve dat ze sneller toegang hebben tot de gegevens die ze nodig hebben. Door de tabel in meerdere tabellen te splitsen, is het de bedoeling dat de uitvoering van de query's veel kleinere tabellen en indexen moet scannen om de benodigde gegevens te vinden. Ongeacht hoe efficiënt een indexstrategie is, het scannen van een index voor een tabel van 50 GB zal altijd veel sneller zijn dan een index voor een tabel van 500 GB. Dit geldt ook voor tafelscans, want soms zijn tafelscans gewoon onvermijdelijk.

Wanneer u een gepartitioneerde tabel in de queryplanner introduceert, moet u een paar dingen weten en begrijpen over de queryplanner zelf. Voordat een query daadwerkelijk wordt uitgevoerd, neemt de queryplanner de query en plant hij de meest efficiënte manier om toegang tot de gegevens te krijgen. Door de gegevens over verschillende tabellen te verdelen, kan de planner beslissen tot welke tabellen toegang moet worden verkregen en welke tabellen volledig moeten worden genegeerd, op basis van wat elke tabel bevat.

Dit wordt gedaan door beperkingen toe te voegen aan de opgesplitste tabellen die bepalen welke gegevens in elke tabel zijn toegestaan, en met een goed ontwerp kunnen we de queryplanner een kleine subset van gegevens laten scannen in plaats van het hele ding.

Moet een tabel worden gepartitioneerd?

Partitioneren kan de prestaties op een tafel drastisch verbeteren als het goed wordt gedaan, maar als het verkeerd wordt gedaan of wanneer het niet nodig is, kan het de prestaties verslechteren, zelfs onbruikbaar maken.

Hoe groot is de tafel?

Er is geen echte harde regel voor hoe groot een tabel moet zijn voordat partitioneren een optie is, maar op basis van databasetoegangstrends zullen databasegebruikers en -beheerders de prestaties van een specifieke tabel gaan verminderen naarmate deze groter wordt. Over het algemeen moet partitionering alleen worden overwogen als iemand zegt:"Ik kan X niet doen omdat de tabel te groot is." Voor sommige hosts kan 200 GB het juiste moment zijn om te partitioneren, voor anderen is het misschien tijd om te partitioneren wanneer het 1TB bereikt.

Als wordt vastgesteld dat de tabel "te groot" is, is het tijd om naar de toegangspatronen te kijken. Ofwel door de applicaties te kennen die toegang hebben tot de database, of door logs te monitoren en queryrapporten te genereren met zoiets als pgBadger, kunnen we zien hoe een tabel wordt benaderd, en afhankelijk van hoe deze wordt benaderd, kunnen we opties hebben voor een goede partitioneringsstrategie.

Lees ons vorige artikel over pgBadger voor meer informatie over pgBadger en het gebruik ervan.

Is table bloat een probleem?

Bijgewerkte en verwijderde rijen resulteren in dode tupels die uiteindelijk moeten worden opgeruimd. Het stofzuigen van tafels, handmatig of automatisch, gaat over elke rij in de tafel en bepaalt of deze moet worden teruggewonnen of alleen moet worden gelaten. Hoe groter de tabel, hoe langer dit proces duurt en hoe meer systeembronnen er worden gebruikt. Zelfs als 90% van een tabel onveranderlijke gegevens zijn, moet deze elke keer dat een vacuüm wordt uitgevoerd worden gescand. Door de tafel te partitioneren, kan de tafel die moet worden gestofzuigd worden teruggebracht tot kleinere, waardoor de hoeveelheid onveranderlijke gegevens die moet worden gescand, wordt verminderd, minder tijd nodig is voor het stofzuigen en er meer systeembronnen worden vrijgemaakt voor gebruikerstoegang in plaats van systeemonderhoud.

Hoe worden gegevens verwijderd, of helemaal niet?

Als gegevens volgens een schema worden verwijderd, bijvoorbeeld gegevens die ouder zijn dan 4 jaar, worden verwijderd en gearchiveerd, kan dit resulteren in zware verwijderinstructies die enige tijd kunnen duren om te worden uitgevoerd, en zoals eerder vermeld, dode rijen creëren die moeten worden opgezogen. Als een goede partitioneringsstrategie wordt geïmplementeerd, kan een DELETE-instructie van meerdere uren met daarna vacuümonderhoud worden omgezet in een DROP TABLE-instructie van één minuut op een oude maandelijkse tafel zonder vacuümonderhoud.

Hoe moet de tabel worden gepartitioneerd?

De sleutels voor toegangspatronen staan ​​in de WHERE-clausule en de JOIN-voorwaarden. Elke keer dat een query kolommen specificeert in de WHERE- en JOIN-clausules, vertelt het de database "dit zijn de gegevens die ik wil". Net zoals het ontwerpen van indexen die zich op deze clausules richten, zijn partitioneringsstrategieën afhankelijk van het targeten van deze kolommen om gegevens te scheiden en de query toegang te geven tot zo min mogelijk partities.

Voorbeelden:

  1. Een transactietabel, met een datumkolom die altijd wordt gebruikt in een waar-clausule.
  2. Een klantentabel met locatiekolommen, zoals het land van verblijf, die altijd wordt gebruikt in waar-clausules.

De meest gebruikelijke kolommen om op te focussen voor partitionering zijn meestal tijdstempels, aangezien een groot deel van de gegevens meestal historische informatie is en waarschijnlijk nogal voorspelbare gegevens zullen bevatten die over verschillende tijdsgroepen zijn verspreid.

Bepaal de gegevensspreiding

Zodra we hebben vastgesteld op welke kolommen we moeten partitioneren, moeten we kijken naar de spreiding van gegevens, met als doel partitiegroottes te maken die de gegevens zo gelijkmatig mogelijk over de verschillende onderliggende partities verdelen.

severalnines=# SELECT DATE_TRUNC('year', view_date)::DATE, COUNT(*) FROM website_views GROUP BY 1 ORDER BY 1;
 date_trunc |  count
------------+----------
 2013-01-01 | 11625147
 2014-01-01 | 20819125
 2015-01-01 | 20277739
 2016-01-01 | 20584545
 2017-01-01 | 20777354
 2018-01-01 |   491002
(6 rows)

In dit voorbeeld kappen we de tijdstempelkolom af tot een jaartabel, wat resulteert in ongeveer 20 miljoen rijen per jaar. Als al onze zoekopdrachten een datum(s) of datumbereik(en) specificeren, en de opgegeven zoekopdrachten meestal gegevens binnen een enkel jaar beslaan, kan dit een goede startstrategie zijn voor partitionering, aangezien dit zou resulteren in één enkele tabel per jaar , met een beheersbaar aantal rijen per tabel.

Download de whitepaper vandaag PostgreSQL-beheer en -automatisering met ClusterControlLees wat u moet weten om PostgreSQL te implementeren, bewaken, beheren en schalenDownload de whitepaper

Een gepartitioneerde tabel maken

Er zijn een aantal manieren om gepartitioneerde tabellen te maken, maar we zullen ons voornamelijk concentreren op het meest veelzijdige type dat beschikbaar is, op triggers gebaseerde partitionering. Dit vereist handmatige installatie en een beetje codering in de plpgsql-proceduretaal om aan de slag te gaan.

Het werkt door een bovenliggende tabel te hebben die uiteindelijk leeg zal worden (of leeg blijft als het een nieuwe tabel is), en onderliggende tabellen die de bovenliggende tabel ERVAREN. Wanneer de bovenliggende tabel wordt opgevraagd, worden de onderliggende tabellen ook doorzocht op gegevens vanwege de INHERIT toegepast op de onderliggende tabellen. Aangezien onderliggende tabellen echter alleen subsets van de bovenliggende gegevens bevatten, voegen we een CONSTRAINT toe aan de tabel die een CONTROLE uitvoert en controleert of de gegevens overeenkomen met wat is toegestaan ​​in de tabel. Dit doet twee dingen:ten eerste weigert het gegevens die er niet thuishoren, en ten tweede vertelt het de queryplanner dat alleen gegevens die overeenkomen met deze CHECK CONSTRAINT zijn toegestaan ​​in deze tabel, dus als u zoekt naar gegevens die niet overeenkomen met de tabel, doe het dan niet niet eens de moeite nemen om het te doorzoeken.

Ten slotte passen we een trigger toe op de bovenliggende tabel die een opgeslagen procedure uitvoert die beslist in welke onderliggende tabel de gegevens moeten worden geplaatst.

Tabel maken

Het maken van de bovenliggende tabel is net als het maken van elke andere tabel.

severalnines=# CREATE TABLE data_log (data_log_sid SERIAL PRIMARY KEY,
  date TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
  event_details VARCHAR);
CREATE TABLE

Onderliggende tabellen maken

Het maken van de onderliggende tabellen is vergelijkbaar, maar brengt enkele toevoegingen met zich mee. Omwille van de organisatie laten we onze onderliggende tabellen in een apart schema bestaan. Doe dit voor elke kindertafel en pas de details dienovereenkomstig aan.

OPMERKING:De naam van de reeks die in de nextval() wordt gebruikt, komt van de reeks die de ouder heeft gemaakt. Dit is cruciaal voor alle onderliggende tabellen om dezelfde volgorde te gebruiken.

severalnines=# CREATE SCHEMA part;
CREATE SCHEMA

severalnines=# CREATE TABLE part.data_log_2018 (data_log_sid integer DEFAULT nextval('public.data_log_data_log_sid_seq'::regclass),
  date TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
  event_details VARCHAR)
 INHERITS (public.data_log);
CREATE TABLE

severalnines=# ALTER TABLE ONLY part.data_log_2018
    ADD CONSTRAINT data_log_2018_pkey PRIMARY KEY (data_log_sid);
ALTER TABLE

severalnines=# ALTER TABLE part.data_log_2018 ADD CONSTRAINT data_log_2018_date CHECK (date >= '2018-01-01' AND date < '2019-01-01');
ALTER TABLE

Functie en trigger maken

Ten slotte maken we onze opgeslagen procedure en voegen we de trigger toe aan onze bovenliggende tabel.

severalnines=# CREATE OR REPLACE FUNCTION 
 public.insert_trigger_table()
  RETURNS trigger
  LANGUAGE plpgsql
 AS $function$
 BEGIN
     IF NEW.date >= '2018-01-01' AND NEW.date < '2019-01-01' THEN
         INSERT INTO part.data_log_2018 VALUES (NEW.*);
         RETURN NULL;
     ELSIF NEW.date >= '2019-01-01' AND NEW.date < '2020-01-01' THEN
         INSERT INTO part.data_log_2019 VALUES (NEW.*);
         RETURN NULL;
     END IF;
 END;
 $function$;
CREATE FUNCTION

severalnines=# CREATE TRIGGER insert_trigger BEFORE INSERT ON data_log FOR EACH ROW EXECUTE PROCEDURE insert_trigger_table();
CREATE TRIGGER

Probeer het uit

Nu het allemaal is gemaakt, laten we het testen. In deze test heb ik meer jaartabellen toegevoegd voor 2013 - 2020.

Opmerking:de invoegreactie hieronder is 'INSERT 0 0', wat zou suggereren dat er niets is ingevoegd. Dit komt later in dit artikel aan de orde.

severalnines=# INSERT INTO data_log (date, event_details) VALUES ('2018-08-20 15:22:14', 'First insert');
INSERT 0 0

severalnines=# SELECT * FROM data_log WHERE date >= '2018-08-01' AND date < '2018-09-01';
 data_log_sid |            date            | event_details
--------------+----------------------------+---------------
            1 | 2018-08-17 23:01:38.324056 | First insert
(1 row)

Het bestaat, maar laten we eens kijken naar de queryplanner om er zeker van te zijn dat de rij uit de juiste onderliggende tabel komt en dat de bovenliggende tabel helemaal geen rijen retourneert.

severalnines=# EXPLAIN ANALYZE SELECT * FROM data_log;
                                                    QUERY PLAN
------------------------------------------------------------------------------------------------------------------
 Append  (cost=0.00..130.12 rows=5813 width=44) (actual time=0.016..0.019 rows=1 loops=1)
   ->  Seq Scan on data_log  (cost=0.00..1.00 rows=1 width=44) (actual time=0.007..0.007 rows=0 loops=1)
   ->  Seq Scan on data_log_2015  (cost=0.00..21.30 rows=1130 width=44) (actual time=0.001..0.001 rows=0 loops=1)
   ->  Seq Scan on data_log_2013  (cost=0.00..17.80 rows=780 width=44) (actual time=0.001..0.001 rows=0 loops=1)
   ->  Seq Scan on data_log_2014  (cost=0.00..17.80 rows=780 width=44) (actual time=0.001..0.001 rows=0 loops=1)
   ->  Seq Scan on data_log_2016  (cost=0.00..17.80 rows=780 width=44) (actual time=0.001..0.001 rows=0 loops=1)
   ->  Seq Scan on data_log_2017  (cost=0.00..17.80 rows=780 width=44) (actual time=0.001..0.001 rows=0 loops=1)
   ->  Seq Scan on data_log_2018  (cost=0.00..1.02 rows=2 width=44) (actual time=0.005..0.005 rows=1 loops=1)
   ->  Seq Scan on data_log_2019  (cost=0.00..17.80 rows=780 width=44) (actual time=0.001..0.001 rows=0 loops=1)
   ->  Seq Scan on data_log_2020  (cost=0.00..17.80 rows=780 width=44) (actual time=0.001..0.001 rows=0 loops=1)
 Planning time: 0.373 ms
 Execution time: 0.069 ms
(12 rows)

Goed nieuws, de enkele rij die we hebben ingevoegd, belandde in de tabel van 2018, waar hij thuishoort. Maar zoals we kunnen zien, specificeert de query geen waar-clausule met behulp van de datumkolom, dus om alles op te halen, voerden de queryplanner en uitvoering een sequentiële scan uit op elke afzonderlijke tabel.

Laten we vervolgens testen met een waar-clausule.

severalnines=# EXPLAIN ANALYZE SELECT * FROM data_log WHERE date >= '2018-08-01' AND date < '2018-09-01';
                                                                   QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
 Append  (cost=0.00..2.03 rows=2 width=44) (actual time=0.013..0.014 rows=1 loops=1)
   ->  Seq Scan on data_log  (cost=0.00..1.00 rows=1 width=44) (actual time=0.007..0.007 rows=0 loops=1)
         Filter: ((date >= '2018-08-01 00:00:00'::timestamp without time zone) AND (date < '2018-09-01 00:00:00'::timestamp without time zone))
   ->  Seq Scan on data_log_2018  (cost=0.00..1.03 rows=1 width=44) (actual time=0.006..0.006 rows=1 loops=1)
         Filter: ((date >= '2018-08-01 00:00:00'::timestamp without time zone) AND (date < '2018-09-01 00:00:00'::timestamp without time zone))
 Planning time: 0.591 ms
 Execution time: 0.041 ms
(7 rows)

Hier kunnen we zien dat de queryplanner en -uitvoering een sequentiële scan hebben uitgevoerd op twee tabellen, de bovenliggende en de onderliggende tabel voor 2018. Er zijn onderliggende tabellen voor de jaren 2013 - 2020, maar die andere dan 2018 zijn nooit geopend omdat de waar-clausule heeft een bereik dat alleen binnen 2018 valt. De queryplanner heeft alle andere tabellen uitgesloten omdat de CHECK CONSTRAINT het onmogelijk acht dat de gegevens in die tabellen voorkomen.

Werkende partities met strikte ORM-tools of ingevoegde rijvalidatie

Zoals eerder vermeld, retourneert het voorbeeld dat we hebben gebouwd een 'INSERT 0 0', ook al hebben we een rij ingevoegd. Als de toepassingen die gegevens in deze gepartitioneerde tabellen invoegen, erop vertrouwen dat de ingevoegde rijen correct zijn, zullen deze mislukken. Er is een oplossing, maar het voegt een extra laag complexiteit toe aan de gepartitioneerde tabel, dus kan worden genegeerd als dit scenario geen probleem is voor de toepassingen die de gepartitioneerde tabel gebruiken.

Een weergave gebruiken in plaats van de bovenliggende tabel.

De oplossing voor dit probleem is om een ​​weergave te maken die de bovenliggende tabel opvraagt, en INSERT-instructies naar de weergave te leiden. Invoegen in een weergave klinkt misschien gek, maar dat is waar de trigger op de weergave binnenkomt.

severalnines=# CREATE VIEW data_log_view AS 
 SELECT data_log.data_log_sid,
     data_log.date,
     data_log.event_details
    FROM data_log;
CREATE VIEW

severalnines=# ALTER VIEW data_log_view ALTER COLUMN data_log_sid SET default nextval('data_log_data_log_sid_seq'::regclass);
ALTER VIEW

Het opvragen van deze weergave ziet er net zo uit als het opvragen van de hoofdtabel, en zowel WHERE-clausules als JOINS werken zoals verwacht.

Bekijk specifieke functie en trigger

In plaats van de functie en trigger te gebruiken die we eerder hebben gedefinieerd, zullen ze allebei iets anders zijn. Wijzigingen in vet.

CREATE OR REPLACE FUNCTION public.insert_trigger_view()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
BEGIN
    IF NEW.date >= '2018-01-01' AND NEW.date < '2019-01-01' THEN
        INSERT INTO part.data_log_2018 VALUES (NEW.*);
        RETURN NEW;

    ELSIF NEW.date >= '2019-01-01' AND NEW.date < '2020-01-01' THEN
        INSERT INTO part.data_log_2019 VALUES (NEW.*);
        RETURN NEW;

    END IF;
END;
$function$;

severalnines=# CREATE TRIGGER insert_trigger INSTEAD OF INSERT ON data_log_view FOR EACH ROW EXECUTE PROCEDURE insert_trigger_view();

De "INSTEAD OF"-definitie neemt het insert-commando in de view over (wat sowieso niet zou werken) en voert in plaats daarvan de functie uit. De functie die we hebben gedefinieerd, heeft een zeer specifieke vereiste om een ​​'RETURN NEW;' te doen nadat het invoegen in de onderliggende tabellen is voltooid. Zonder dit (of het doen zoals we eerder deden met 'RETURN NULL') zal resulteren in 'INSERT 0 0' in plaats van 'INSERT 0 1' zoals we zouden verwachten.

Voorbeeld:

severalnines=# INSERT INTO data_log_view (date, event_details) VALUES ('2018-08-20 18:12:48', 'First insert on the view');
INSERT 0 1

severalnines=# EXPLAIN ANALYZE SELECT * FROM data_log_view WHERE date >= '2018-08-01' AND date < '2018-09-01';
                                                                   QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
 Append  (cost=0.00..2.03 rows=2 width=44) (actual time=0.015..0.017 rows=2 loops=1)
   ->  Seq Scan on data_log  (cost=0.00..1.00 rows=1 width=44) (actual time=0.009..0.009 rows=0 loops=1)
         Filter: ((date >= '2018-08-01 00:00:00'::timestamp without time zone) AND (date < '2018-09-01 00:00:00'::timestamp without time zone))
   ->  Seq Scan on data_log_2018  (cost=0.00..1.03 rows=1 width=44) (actual time=0.006..0.007 rows=2 loops=1)
         Filter: ((date >= '2018-08-01 00:00:00'::timestamp without time zone) AND (date < '2018-09-01 00:00:00'::timestamp without time zone))
 Planning time: 0.633 ms
 Execution time: 0.048 ms
(7 rows)

severalnines=# SELECT * FROM data_log_view WHERE date >= '2018-08-01' AND date < '2018-09-01';
 data_log_sid |        date         |      event_details
--------------+---------------------+--------------------------
            1 | 2018-08-20 15:22:14 | First insert
            2 | 2018-08-20 18:12:48 | First insert on the view
(2 rows)

Toepassingen die testen of de ingevoegde 'rowcount' correct is, zullen merken dat deze oplossing werkt zoals verwacht. In dit voorbeeld hebben we _view toegevoegd aan onze weergave en opgeslagen procedure, maar als het gewenst is dat de tabel wordt gepartitioneerd zonder dat gebruikers het weten / toepassingswijziging, dan zouden we de bovenliggende tabel hernoemen naar data_log_parent, en de weergave aanroepen door de oude naam van de bovenliggende tabel.

Een rij bijwerken en de gepartitioneerde kolomwaarde wijzigen

Een ding om op te letten, is dat als u een update uitvoert op de gegevens in de gepartitioneerde tabel en de waarde van de kolom wordt gewijzigd in iets dat niet is toegestaan ​​door de beperking, er een fout zal optreden. Als dit type update nooit zal plaatsvinden, kan het worden genegeerd, maar als het een mogelijkheid is, moet een nieuwe trigger voor UPDATE-processen worden geschreven die de rij effectief van de oude onderliggende partitie verwijdert en een nieuwe invoegt in de nieuwe onderliggende doelpartitie.

Toekomstige partities maken

Toekomstige partities maken kan op een paar verschillende manieren, elk met hun voor- en nadelen.

Toekomstige maker van partities

Er kan een extern programma worden geschreven om toekomstige partities X keer te maken voordat ze nodig zijn. In een partitioneringsvoorbeeld dat op een datum is gepartitioneerd, kan de volgende benodigde partitie om te maken (in ons geval 2019) ergens in december worden gemaakt. Dit kan een handmatig script zijn dat wordt uitgevoerd door de databasebeheerder, of zo worden ingesteld dat cron het indien nodig laat uitvoeren. Jaarlijkse partities zouden betekenen dat het eenmaal per jaar wordt uitgevoerd, maar dagelijkse partities zijn gebruikelijk, en een dagelijkse cron-taak zorgt voor een gelukkiger DBA.

Automatische partitiemaker

Met de kracht van plpgsql kunnen we fouten vastleggen als we gegevens proberen in te voegen in een onderliggende partitie die niet bestaat, en meteen de benodigde partitie maken en vervolgens opnieuw proberen in te voegen. Deze optie werkt goed, behalve in het geval dat veel verschillende clients tegelijkertijd vergelijkbare gegevens invoegen, een race-conditie kunnen veroorzaken waarbij de ene client de tabel maakt, terwijl een andere probeert dezelfde tabel te maken en een foutmelding krijgt dat deze al bestaat. Slimme en geavanceerde plpgsql-programmering kan dit oplossen, maar of het de moeite waard is, staat ter discussie. Als deze raceconditie niet optreedt vanwege de invoegpatronen, hoeft u zich geen zorgen te maken.

Partities verwijderen

Als regels voor het bewaren van gegevens voorschrijven dat gegevens na een bepaalde tijd worden verwijderd, wordt dit gemakkelijker met gepartitioneerde tabellen als ze zijn gepartitioneerd op een datumkolom. Als we gegevens van 10 jaar oud moeten verwijderen, kan dat zo simpel zijn als:

severalnines=# DROP TABLE part.data_log_2007;
DROP TABLE

Dit is veel sneller en efficiënter dan een 'DELETE'-instructie, omdat het niet resulteert in dode tupels die met een vacuüm moeten worden schoongemaakt.

Opmerking:als tabellen uit de partitie-setup worden verwijderd, moet de code in de triggerfuncties ook worden gewijzigd om de datum niet naar de verwijderde tabel te verwijzen.

Wat u moet weten voordat u gaat partitioneren

Partitioneringstabellen kunnen een drastische verbetering van de prestaties bieden, maar het kan het ook erger maken. Alvorens naar productieservers te pushen, moet de partitioneringsstrategie uitgebreid worden getest op gegevensconsistentie, prestatiesnelheid, alles. Het partitioneren van een tafel heeft een paar bewegende delen, ze moeten allemaal worden getest om er zeker van te zijn dat er geen problemen zijn.

Als het gaat om het bepalen van het aantal partities, wordt sterk aangeraden om het aantal onderliggende tafels onder de 1000 tafels te houden, en indien mogelijk zelfs lager. Zodra het aantal onderliggende tabellen boven de ~1000 komt, beginnen de prestaties achteruit te gaan, omdat de queryplanner zelf veel langer nodig heeft om het queryplan te maken. Het is niet ongehoord dat een queryplan vele seconden duurt, terwijl de daadwerkelijke uitvoering slechts enkele milliseconden duurt. Als duizenden vragen per minuut worden afgehandeld, kunnen enkele seconden applicaties tot stilstand brengen.

De plpgsql trigger-opgeslagen procedures kunnen ook ingewikkeld worden, en als ze te ingewikkeld zijn, ook de prestaties vertragen. De opgeslagen procedure wordt één keer uitgevoerd voor elke rij die in de tabel wordt ingevoegd. Als het uiteindelijk te veel verwerking voor elke rij doet, kunnen invoegingen te traag worden. Prestatietests zorgen ervoor dat het nog steeds binnen het acceptabele bereik blijft.

Wees creatief

Het partitioneren van tabellen in PostgreSQL kan zo geavanceerd zijn als nodig is. In plaats van datumkolommen kunnen tabellen worden gepartitioneerd op een 'land'-kolom, met een tabel voor elk land. Partitioneren kan op meerdere kolommen, zoals zowel een ‘datum’ als een ‘land’ kolom. Dit maakt de opgeslagen procedure voor het verwerken van de inserts complexer, maar het is 100% mogelijk.

Onthoud dat het doel van partitionering is om extreem grote tabellen op te splitsen in kleinere, en dit op een goed doordachte manier te doen zodat de queryplanner sneller toegang heeft tot de gegevens dan in de grotere originele tabel.

Declaratieve partitionering

In PostgreSQL 10 en later is een nieuwe partitioneringsfunctie 'Declarative Partitioning' geïntroduceerd. Het is een eenvoudigere manier om partities in te stellen, maar heeft enkele beperkingen. Als de beperkingen acceptabel zijn, zal het waarschijnlijk sneller werken dan de handmatige partitie-instelling, maar uitgebreide tests zullen dat verifiëren.

De officiële postgresql-documentatie bevat informatie over Declarative Partitioning en hoe het werkt. Het is nieuw in PostgreSQL 10, en met versie 11 van PostgreSQL aan de horizon op het moment van schrijven, zijn enkele van de beperkingen opgelost, maar niet allemaal. Naarmate PostgreSQL evolueert, kan Declarative Partitioning een volledige vervanging worden voor de meer complexe partitionering die in dit artikel wordt behandeld. Tot die tijd kan declaratieve partitionering een eenvoudiger alternatief zijn als geen van de beperkingen de partitioneringsbehoeften beperkt.

Declaratieve partitioneringsbeperkingen

De PostgreSQL-documentatie behandelt alle beperkingen met dit type partitionering in PostgreSQL 10, maar een goed overzicht is te vinden op The Official PostgreSQL Wiki, die de beperkingen in een gemakkelijker te lezen formaat opsomt, en ook aangeeft welke zijn opgelost in de komende PostgreSQL 11.

Vraag het de community

Databasebeheerders over de hele wereld ontwerpen al lange tijd geavanceerde en aangepaste partitioneringsstrategieën, en velen van ons hangen rond in IRC en mailinglijsten. Als je hulp nodig hebt bij het bepalen van de beste strategie, of als je gewoon een bug in een opgeslagen procedure wilt oplossen, staat de community voor je klaar.

  • IRC
    Freenode heeft een zeer actief kanaal genaamd #postgres, waar gebruikers elkaar helpen concepten te begrijpen, fouten op te lossen of andere bronnen te vinden.
  • Mailinglijsten
    PostgreSQL heeft een handvol mailinglijsten waaraan u kunt deelnemen. Vragen / problemen met een langere vorm kunnen hier worden verzonden en kunnen op elk moment veel meer mensen bereiken dan IRC. De lijsten zijn te vinden op de PostgreSQL-website en de lijsten pgsql-general of pgsql-admin zijn goede bronnen.

  1. Een zeer beschikbare canvas LMS implementeren met een PostgreSQL-databasecluster

  2. Maak een datum van dag, maand en jaar met T-SQL

  3. SQL Server Log Verzending &Disaster Recovery Installatie en configuratie -4

  4. De verbinding tussen client en server configureren Oracle 10g