PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Miglioramenti al partizionamento in PostgreSQL 11

Un sistema di partizionamento in PostgreSQL è stato aggiunto per la prima volta in PostgreSQL 8.1 dal fondatore di 2ndQuadrant Simon Riggs . Si basava sull'ereditarietà delle relazioni e utilizzava una nuova tecnica per escludere le tabelle dalla scansione di una query, chiamata "esclusione di vincoli". Sebbene all'epoca fosse un enorme passo avanti, al giorno d'oggi è considerato ingombrante da usare oltre che lento e quindi da sostituire.

Nella versione 10, è stato sostituito grazie a sforzi eroici da Amit Langote con “partizione dichiarativa” in stile moderno. Questa nuova tecnologia significava che non era più necessario scrivere codice manualmente per instradare le tuple alle loro partizioni corrette e non era più necessario dichiarare manualmente i vincoli corretti per ciascuna partizione:il sistema faceva queste cose automaticamente per te.

Purtroppo, in PostgreSQL 10 è praticamente tutto ciò che ha fatto. A causa dell'assoluta complessità e dei limiti di tempo, c'erano molte cose che mancavano nell'implementazione di PostgreSQL 10. Robert Haas ne ha parlato nella PGConf.EU di Varsavia.

Molte persone hanno lavorato per migliorare la situazione per PostgreSQL 11; ecco il mio tentativo di riconteggio. Li ho suddivisi in tre aree:

  1. Nuove funzionalità di partizionamento
  2. Migliore supporto DDL per tabelle partizionate
  3. Ottimizzazioni delle prestazioni.

Nuove funzionalità di partizionamento

In PostgreSQL 10, le tue tabelle partizionate possono essere così in RANGE e ELENCO modalità. Questi sono strumenti potenti su cui basare molti database del mondo reale, ma per molti altri progetti è necessaria la nuova modalità aggiunta in PostgreSQL 11:HASH partizionamento . Molti clienti hanno bisogno di questo e di Amul Sul ha lavorato duramente per renderlo possibile. Ecco un semplice esempio:

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

Non è obbligatorio utilizzare lo stesso valore del modulo per tutte le partizioni; questo ti consente di creare più partizioni in un secondo momento e di ridistribuire le righe una partizione alla volta, se necessario.

Un'altra funzionalità molto utile, scritta da Amit Khandekar è la possibilità di consentire AGGIORNAMENTO per spostare le righe da una partizione all'altra — ovvero, se c'è una modifica nei valori della colonna di partizionamento, la riga viene spostata automaticamente nella partizione corretta. In precedenza, quell'operazione avrebbe generato un errore.

Un'altra nuova funzionalità, scritta da Amit Langote e veramente tuo , è che INSERIRE AGGIORNAMENTO CONFLITTO può essere applicato a tabelle partizionate . In precedenza questo comando falliva se mirava a una tabella partizionata. Potresti farlo funzionare sapendo esattamente in quale partizione finirebbe la riga, ma non è molto conveniente. Non esaminerò i dettagli di quel comando, ma se hai mai desiderato di avere UPSERT in Postgres, questo è tutto. Un avvertimento è che UPDATE action potrebbe non spostare la riga in un'altra partizione.

Infine, un'altra simpatica nuova funzionalità in PostgreSQL 11, questa volta di Jeevan Ladhe, Beena Emerson, Ashutosh Bapat, Rahila Syed, e Robert Haas è supporto per una partizione predefinita in una tabella partizionata , ovvero una partizione che riceve tutte le righe che non rientrano in nessuna delle partizioni regolari. Tuttavia, sebbene sia piacevole sulla carta, questa funzionalità non è molto comoda per le impostazioni di produzione perché alcune operazioni richiedono un blocco più pesante con le partizioni predefinite rispetto a senza. Esempio:la creazione di una nuova partizione richiede la scansione della partizione predefinita per determinare che nessuna riga esistente corrisponda ai limiti della nuova partizione. Forse in futuro questi requisiti di blocco verranno abbassati, ma nel frattempo il mio consiglio è di non usarlo.

Migliore supporto DDL

In PostgreSQL 10, alcuni DDL si rifiutavano di funzionare se applicati a una tabella partizionata e richiedevano di elaborare ciascuna partizione individualmente. In PostgreSQL 11 abbiamo corretto alcune di queste limitazioni, come precedentemente annunciato da Simon Riggs. Innanzitutto, ora puoi utilizzare CREA INDEX su una tabella partizionata , una caratteristica scritta da te veramente. Questo può essere visto solo come una questione di riduzione della noia:invece di ripetere il comando per ogni partizione (e assicurandoti di non dimenticarlo mai per ogni nuova partizione), puoi farlo solo una volta per la tabella partizionata genitore e si applica automaticamente a tutte le partizioni, esistenti e future.

Una cosa interessante da tenere a mente è la corrispondenza degli indici esistenti nelle partizioni. Come sai, la creazione di un indice è una proposta bloccante, quindi meno tempo ci vuole, meglio è. Ho scritto questa funzionalità in modo che gli indici esistenti nella partizione vengano confrontati con gli indici in fase di creazione e, se ci sono corrispondenze, non è necessario scansionare la partizione per creare nuovi indici:verrebbero utilizzati gli indici esistenti.

Insieme a questo, anche da te veramente, puoi anche creare UNICO vincoli, nonché CHIAVE PRIMARIA vincoli . Due avvertenze:in primo luogo, la chiave di partizione deve far parte della chiave primaria. Ciò consente di eseguire i controlli univoci localmente per partizione, evitando gli indici globali. In secondo luogo, non è ancora possibile avere chiavi esterne che facciano riferimento a queste chiavi primarie. Ci sto lavorando per PostgreSQL 12.

Un'altra cosa che puoi fare (grazie alla stessa persona) è creare PER OGNI RIGA trigger su una tabella partizionata , e applicalo a tutte le partizioni (esistenti e future). Come effetto collaterale, potresti aver differito unico vincoli sulle tabelle partizionate. Un avvertimento:solo DOPO i trigger sono consentiti, finché non scopriamo come gestire PRIMA trigger che spostano le righe in una partizione diversa.

Infine, una tabella partizionata può avere CHIAVE ESTERA vincoli . Questo è molto utile per partizionare tabelle dei fatti di grandi dimensioni evitando riferimenti penzolanti, che tutti detestano. Il mio collega Gabriele Bartolini mi ha preso in grembo quando ha saputo che avevo scritto e commesso questo, urlando che questo era un punto di svolta e come potevo essere così insensibile da non informarlo di questo. Io, continuo ad hackerare il codice per divertimento.

Lavori prestazionali

In precedenza, la pre-elaborazione delle query per scoprire quali partizioni non analizzare (esclusione dei vincoli) era piuttosto semplicistica e lenta. Ciò è stato migliorato dall'ammirevole lavoro di squadra portato avanti da Amit Langote, David Rowley, Beena Emerson, Dilip Kumar per introdurre prima la "potatura più rapida" e la "potatura a runtime" basata su di essa in seguito. Il risultato è molto più potente e più veloce (David Rowley già descritto in un articolo precedente.) Dopo tutto questo sforzo, l'eliminazione delle partizioni viene applicata in tre punti nella vita di una query:

  1. Al momento del piano di query,
  2. Quando vengono ricevuti i parametri della query,
  3. Ad ogni punto in cui un nodo di query passa valori come parametri a un altro nodo.

Si tratta di un notevole miglioramento rispetto al sistema originale che poteva essere applicato solo al momento del piano di query e credo che soddisferà molti.

Puoi vedere questa funzione in azione confrontando l'output di EXPLAIN per una query prima e dopo aver disattivato enable_partition_pruning opzione. Come esempio molto semplicistico, confronta questo piano senza potatura:

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

con quello prodotto con la potatura:

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

Sono sicuro che lo troverai avvincente. Puoi vedere un sacco di esempi più sofisticati esaminando il file previsto dei test di regressione.

Un altro elemento è stata l'introduzione dei join partizionali, da parte di Ashutosh Bapat . L'idea qui è che se hai due tabelle partizionate e sono partizionate in modo identico, quando vengono unite puoi unire ogni partizione su un lato alla sua partizione corrispondente sull'altro lato; questo è molto meglio che unire ogni partizione su un lato a ogni partizione sull'altro lato. Il fatto che gli schemi di partizione debbano corrispondere esattamente può far sembrare improbabile che questo abbia un uso molto reale nel mondo reale, ma in realtà ci sono molte situazioni in cui ciò si applica. Esempio:una tabella degli ordini e la sua tabella order_items corrispondente. Per fortuna, c'è già molto lavoro per allentare questa restrizione.

L'ultimo elemento che voglio menzionare sono gli aggregati partizionati, di Jeevan Chalke, Ashutosh Bapat, e Robert Haas . Questa ottimizzazione significa che un'aggregazione che include le chiavi di partizione nel GRUPPO PER La clausola può essere eseguita aggregando separatamente le righe di ciascuna partizione, il che è molto più veloce.

Pensieri conclusivi

Dopo gli sviluppi significativi di questo ciclo, PostgreSQL ha una storia di partizionamento molto più avvincente. Sebbene ci siano ancora molti miglioramenti da apportare, in particolare per migliorare le prestazioni e la concorrenza di varie operazioni che coinvolgono tabelle partizionate, ora siamo a un punto in cui il partizionamento dichiarativo è diventato uno strumento molto prezioso per soddisfare molti casi d'uso. In 2ndQuadrant continueremo a contribuire con il codice per migliorare PostgreSQL in quest'area e in altre, come abbiamo fatto per ogni singola versione dalla 8.0.