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

Come sfruttare le nuove funzionalità di partizionamento in PostgreSQL 11

Che cos'è il partizionamento?

Il partizionamento suddivide le tabelle di grandi dimensioni in parti più piccole, il che aiuta ad aumentare le prestazioni delle query, semplificare le attività di manutenzione, migliorare l'efficienza dell'archiviazione dei dati e velocizzare i backup dei database. Puoi leggere di più sul partizionamento di PostgreSQL nel nostro blog "A Guide to Partitioning Data In PostgreSQL".

Con la recente versione di PostgreSQL 11 ci sono molte nuove fantastiche funzionalità di partizionamento. I dettagli di queste nuove funzionalità di partizionamento verranno trattati in questo blog con alcuni esempi di codice.

Aggiornamento delle chiavi di partizione

Prima di PostgreSQL 11, l'istruzione Update che modifica il valore della chiave di partizione era limitata e non consentita. Questo è ora possibile nella nuova versione. L'istruzione di aggiornamento può modificare il valore della chiave di partizione; in realtà sposta le righe nella tabella delle partizioni corretta. Sotto il cofano esegue fondamentalmente DELETE FROM vecchia partizione e INSERT nella nuova partizione ( DELETE + INSERT).

Va bene, proviamolo. Crea una tabella e verifica come funziona l'aggiornamento sulla chiave di partizione.

CREATE TABLE customers(cust_id bigint NOT NULL,cust_name varchar(32) NOT NULL,cust_address text,
cust_country text)PARTITION BY LIST(cust_country);
CREATE TABLE customer_ind PARTITION OF customers FOR VALUES IN ('ind');
CREATE TABLE customer_jap PARTITION OF customers FOR VALUES IN ('jap');
CREATE TABLE customers_def PARTITION OF customers DEFAULT;
severalnines_v11=# INSERT INTO customers VALUES (2039,'Puja','Hyderabad','ind');
INSERT 0 1
severalnines_v11=#  SELECT * FROM customer_ind;
 cust_id | cust_name | cust_address | cust_country
  2039 | Puja      | Hyderabad    | ind
(1 row)
severalnines_v11=# UPDATE customers SET cust_country ='jap' WHERE cust_id=2039;
UPDATE 1
--  it moved the row to correct  partition table.
severalnines_v11=# SELECT * FROM customer_ind;
 cust_id | cust_name | cust_address | cust_country
---------+-----------+--------------+--------------
(0 rows)
severalnines_v11=# SELECT * FROM customer_jap;
 cust_id | cust_name | cust_address | cust_country
---------+-----------+--------------+--------------
    2039 | Puja      | Hyderabad    | jap
(1 row)

Attenzione:l'AGGIORNAMENTO uscirà in errore, se non è presente una tabella delle partizioni predefinita e i valori aggiornati non corrispondono ai criteri di partizione in nessuna tabella figlio.

severalnines_v11=#  UPDATE customers1 SET cust_country ='ypp' WHERE cust_id=2039;
2018-11-21 00:13:54.901 IST [1479] ERROR:  no partition of relation "customers1" found for row
2018-11-21 00:13:54.901 IST [1479] DETAIL:  Partition key of the failing row contains (cust_country) = (ypp).
2018-11-21 00:13:54.901 IST [1479] STATEMENT:  UPDATE customers1 SET cust_country ='ypp' WHERE cust_id=2039;
ERROR:  no partition of relation "customers1" found for row
DETAIL:  Partition key of the failing row contains (cust_country) = (ypp).
[ -- the value of cust_country was not mapped to any part table so it failed]

Creazione di una partizione predefinita

La funzione di partizione DEFAULT di PostgreSQL 11 memorizza le tuple che non vengono mappate a nessun'altra partizione. Prima di PostgreSQL 11, queste righe uscivano in errore. Una riga che non è mappata a nessuna tabella delle partizioni verrebbe inserita nella partizione predefinita.

Esempio di laboratorio:il codice paese `USA` non è stato definito nella tabella delle partizioni di seguito, ma viene comunque inserito correttamente nella tabella predefinita.

CREATE TABLE customers_def PARTITION OF customers DEFAULT;
severalnines_v11=#  INSERT INTO customers VALUES (4499,'Tony','Arizona','USA');
INSERT 0 1
severalnines_v11=#  select * FROM customers_def;
 cust_id | cust_name | cust_address | cust_country
---------+-----------+--------------+--------------
    4499 | Tony      | Arizona      | USA

Avvertimento:la partizione predefinita impedirà l'aggiunta di una nuova partizione se il valore della partizione esiste nella tabella predefinita. In questo caso `USA` esisteva nella partizione predefinita, quindi non funzionerà come di seguito.

severalnines_v11=# CREATE TABLE customer_usa PARTITION OF customers FOR VALUES IN ('USA');
2018-11-21 00:46:34.890 IST [1526] ERROR:  updated partition constraint for default partition "customers_def" would be violated by some row
2018-11-21 00:46:34.890 IST [1526] STATEMENT:  CREATE TABLE customer_usa PARTITION OF customers FOR VALUES IN ('USA');ERROR:  updated partition constraint for default partition "customers_def" would be violated by some row
severalnines_v11=#
Resolution - You need to move/remove those rows from Default table, then it will then let you create new part table like below.
severalnines_v11=# DELETE FROM customers_def WHERE cust_country in ('USA'); DELETE 1
severalnines_v11=# CREATE TABLE customer_usa PARTITION OF customers FOR VALUES IN ('USA');
CREATE TABLE
severalnines_v11=#
Nudgets :

La partizione DEFAULT non può essere specificata per la tabella partizionata HASH. Non può esserci più di una tabella DEFAULT per la tabella delle partizioni.

Partizionamento hash

È un nuovo meccanismo di partizione, se non puoi decidere un intervallo o una partizione di elenco (poiché non sei sicuro di quanto sarebbe grande il bucket). Il partizionamento hash risolve questo problema di distribuzione dei dati.

La tabella viene partizionata specificando un modulo e un resto per ciascuna partizione. Ogni partizione conterrà le righe per le quali il valore hash della chiave di partizione diviso per il modulo specificato produrrà il resto specificato. La funzione HASH assicura che le righe siano distribuite per lo più uniformemente in tutta la tabella delle partizioni.

Per cominciare, è necessario decidere quanti numeri della tabella delle partizioni sono necessari e, di conseguenza, è possibile definire modulo e resto; se il modulo è 4, il resto può essere solo da [0-3].

[Modulo - Numero di tabelle | Resto:quale valore del resto va a quale bucket ]

Come configurare una partizione hash

-- hash partition
CREATE TABLE part_hash_test (x int, y text) PARTITION BY hash (x);
-- create child partitions
CREATE TABLE part_hash_test_0 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE part_hash_test_1 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE part_hash_test_2 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE part_hash_test_3 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 3);

Inserisci 50.000 record nella tabella principale:

severalnines_v11=# INSERT INTO part_hash_test SELECT generate_series(0,50000);
INSERT 0 50001

e guarda come ha distribuito i record in modo uniforme nella tabella figlio ...

severalnines_v11=# SELECT count(1),tableoid::regclass FROM part_hash_test GROUP by 2 order by 2 ;
 count |     tableoid
-------+------------------
 12537 | part_hash_test_0
 12473 | part_hash_test_1
 12509 | part_hash_test_2
 12482 | part_hash_test_3
(4 rows)

Non possiamo modificare il numero di partizioni specificate da `Modulus` in precedenza, quindi è necessario pianificare molto prima dei requisiti per il numero di tabelle delle partizioni.

Verrà visualizzato un errore quando si tenta di aggiungere una nuova partizione con un resto diverso.

severalnines_v11=# CREATE TABLE part_hash_test_5 PARTITION OF part_hash_test FOR VALUES
WITH (MODULUS 4, REMAINDER 5);severalnines_v11-#
2018-11-21 01:51:28.966 IST [1675] ERROR:  remainder for hash partition must be less than modulus
2018-11-21 01:51:28.966 IST [1675] STATEMENT:  CREATE TABLE part_hash_test_5 PARTITION OF part_hash_test FOR VALUES  WITH (MODULUS 4, REMAINDER 5);

Il partizionamento hash può funzionare su qualsiasi tipo di dati e può funzionare anche per il tipo UUID. È sempre consigliabile che il numero di tabelle sia una potenza di 2, inoltre non è obbligatorio utilizzare lo stesso modulo durante la creazione della tabella; questo aiuterà a creare la tabella delle partizioni in un secondo momento come richiesto.

Questa implementazione renderebbe anche il vuoto più veloce e potrebbe consentire l'unione saggia della partizione.

Supporto per chiavi esterne

Prima di PostgreSQL 11, la chiave esterna nella tabella delle partizioni non era supportata. Le chiavi esterne sono ora possibili nella tabella delle partizioni e di seguito è come...

severalnines_v11=# CREATE TABLE customers2 ( cust_id integer PRIMARY KEY );
CREATE TABLE
severalnines_v11=# CREATE TABLE account (
    ac_date   date    NOT NULL,
    cust_id  integer REFERENCES customers2(cust_id),
     amount INTEGER NOT NULL) PARTITION BY RANGE (ac_date);
CREATE TABLE

Creazione automatica dell'indice su tabelle figlio

Nelle versioni precedenti di PostgreSQL era uno sforzo manuale per creare un indice su ogni tabella delle partizioni. In PostgreSQL versione 11, è abbastanza conveniente per gli utenti. Una volta creato l'indice sulla tabella master, creerà automaticamente l'indice con la stessa configurazione su tutte le partizioni figlio esistenti e si occuperà anche di eventuali tabelle delle partizioni future.

Indice creato sulla tabella principale

severalnines_v11=# CREATE index idx_name ON customers(cust_name);
CREATE INDEX

Ha creato automaticamente l'indice su tutte le tabelle figlio come di seguito. (Verifica con tabella catalogo)

severalnines_v11=# SELECT tablename,indexname,indexdef FROM pg_indexes WHERE tablename ilike '%customer_%';
   tablename   |          indexname          |       indexdef
---------------+-----------------------------+------------------------------------------------------------------------------------------
 customer_ind  | customer_ind_cust_name_idx  | CREATE INDEX customer_ind_cust_name_idx ON public.customer_ind USING btree (cust_name)
 customer_jap  | customer_jap_cust_name_idx  | CREATE INDEX customer_jap_cust_name_idx ON public.customer_jap USING btree (cust_name)
 customer_usa  | customer_usa_cust_name_idx  | CREATE INDEX customer_usa_cust_name_idx ON public.customer_usa USING btree (cust_name)
 customers_def | customers_def_cust_name_idx | CREATE INDEX customers_def_cust_name_idx ON public.customers_def USING btree (cust_name)
(4 rows)

L'indice può essere creato solo su una tabella principale, non può essere su una tabella figlio. Gli indici generati automaticamente non possono essere eliminati singolarmente.

Creazione di trigger automatici su tabelle figli

Una volta creato il trigger sulla tabella master, creerà automaticamente il trigger su tutte le tabelle figlie (questo comportamento è simile a quello visto per l'indice).

Possibilità di creare un indice unico

Nella versione 11 è possibile aggiungere indici univoci alla tabella principale che creerà il vincolo univoco su tutte le tabelle figlio esistenti e future tabelle di partizione.

Creiamo una tabella principale con vincoli univoci.

CREATE TABLE uniq_customers(  cust_id bigint NOT NULL, cust_name varchar(32) NOT NULL, cust_address text, cust_country text,cust_email text, unique(cust_email,cust_id,cust_country)  )PARTITION BY LIST(cust_country);

Il vincolo univoco è stato creato automaticamente sulla tabella figlio come di seguito.

severalnines_v11=# SELECT table_name,constraint_name,constraint_type FROM information_schema.table_constraints WHERE table_name ilike '%uniq%' AND constraint_type = 'UNIQUE';
    table_name     |                    constraint_name                    | constraint_type
-------------------+-------------------------------------------------------+-----------------
 uniq_customers    | uniq_customers_cust_email_cust_id_cust_country_key    | UNIQUE
 uniq_customer_ind | uniq_customer_ind_cust_email_cust_id_cust_country_key | UNIQUE
(2 rows)

Attenzione:un vincolo univoco sulla tabella padre non garantisce effettivamente l'univocità nell'intera gerarchia di partizionamento. Non è un vincolo globale, è solo locale.

Scarica il whitepaper oggi Gestione e automazione di PostgreSQL con ClusterControlScopri cosa devi sapere per distribuire, monitorare, gestire e ridimensionare PostgreSQLScarica il whitepaper

Prestazioni delle query più rapide

Eliminazione dinamica delle partizioni

In PostgreSQL 11, la ricerca binaria consente un'identificazione più rapida delle tabelle figlio richieste, indipendentemente dal fatto che siano partizionate LIST o RANGE. La funzione di hashing trova la partizione corrispondente per la partizione HASH. In realtà elimina dinamicamente le tabelle delle partizioni che non sono richieste e migliora le prestazioni della query.

L'eliminazione dinamica della partizione può essere controllata dal parametro `enable_partition_pruning`.

severalnines_v11=# show enable_partition_pruning;
 enable_partition_pruning
--------------------------
 off
(1 row)
severalnines_v11=# EXPLAIN SELECT * from customers where cust_country = 'ind';
                             QUERY PLAN
---------------------------------------------------------------------
 Append  (cost=0.00..18.54 rows=5 width=154)
   ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=154)
         Filter: (cust_country = 'ind'::text)
   ->  Seq Scan on customer_jap  (cost=0.00..1.00 rows=1 width=154)
         Filter: (cust_country = 'ind'::text)
   ->  Seq Scan on customer_usa  (cost=0.00..15.50 rows=2 width=154)
         Filter: (cust_country = 'ind'::text)
   ->  Seq Scan on customers_def  (cost=0.00..1.00 rows=1 width=154)
         Filter: (cust_country = 'ind'::text)
(9 rows)
Enabled the parameter to ON.
severalnines_v11=# set enable_partition_pruning TO on;
SET
severalnines_v11=# EXPLAIN SELECT * from customers where cust_country = 'ind';
                             QUERY PLAN
--------------------------------------------------------------------
 Append  (cost=0.00..1.02 rows=1 width=154)
   ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=154)
         Filter: (cust_country = 'ind'::text)
(3 rows)

L'altra fantastica implementazione è così.

Eliminazione partizione tempo di esecuzione

Nelle versioni di PostgreSQL precedenti alla 11, l'eliminazione delle partizioni può avvenire solo al momento del piano; planner richiede un valore della chiave di partizione per identificare la partizione corretta. Questo comportamento è stato risolto in PostgreSQL 11, poiché il pianificatore del tempo di esecuzione saprebbe quale valore viene fornito e in base a tale selezione/eliminazione della partizione è possibile e funzionerebbe molto più velocemente. Il caso d'uso può essere una query che utilizza un parametro (istruzione preparata) OPPURE una sottoquery che fornisce il valore come parametro.

Example : cus_country is partition key and getting value from subquery
severalnines_v11=# explain analyze  select * from customers WHERE cust_country = (select cust_count_x FROM test_execution_prun1);
                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Append  (cost=23.60..42.14 rows=5 width=154) (actual time=0.019..0.020 rows=0 loops=1)
   InitPlan 1 (returns $0)
     ->  Seq Scan on test_execution_prun1  (cost=0.00..23.60 rows=1360 width=32) (actual time=0.006..0.007 rows=1 loops=1)
   ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=154) (never executed)
         Filter: (cust_country = $0)
   ->  Seq Scan on customer_jap  (cost=0.00..1.00 rows=1 width=154) (never executed)
         Filter: (cust_country = $0)
   ->  Seq Scan on customer_usa  (cost=0.00..15.50 rows=2 width=154) (never executed)
         Filter: (cust_country = $0)
   ->  Seq Scan on customers_def  (cost=0.00..1.00 rows=1 width=154) (actual time=0.003..0.003 rows=0 loops=1)
         Filter: (cust_country = $0)
 Planning Time: 0.237 ms
 Execution Time: 0.057 ms
(13 rows)

Nella spiegazione del piano sopra, possiamo vedere che, al momento dell'esecuzione, il pianificatore ha identificato al volo la tabella delle partizioni corretta in base al valore del parametro, ed è stata eseguita molto più velocemente e non ha dedicato tempo alla scansione/ciclo su un'altra tabella delle partizioni (vedi mai sezione eseguita in spiegare il piano sopra). Questo è molto potente e ha dato il via a una nuova era di miglioramento delle prestazioni nel partizionamento.

Partizione aggregata saggia

Parametro:enable_partitionwise_aggregate

Se la chiave di partizione corrisponde alla chiave di raggruppamento, ogni partizione produrrà un insieme discreto di gruppi invece di scansionare tutta la partizione in una volta. Eseguirà l'aggregazione parallela per ogni partizione e durante il risultato finale concatenerà tutti i risultati.

severalnines_v11=# explain SELECT count(1),cust_country FROM customers GROUP BY 2;
                                 QUERY PLAN
----------------------------------------------------------------------------
 HashAggregate  (cost=21.84..23.84 rows=200 width=40)
   Group Key: customer_ind.cust_country
   ->  Append  (cost=0.00..19.62 rows=443 width=32)
         ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=32)
         ->  Seq Scan on customer_jap  (cost=0.00..1.00 rows=1 width=32)
         ->  Seq Scan on customer_usa  (cost=0.00..14.40 rows=440 width=32)
         ->  Seq Scan on customers_def  (cost=0.00..1.00 rows=1 width=32)
(7 rows)
severalnines_v11=# SET  enable_partitionwise_aggregate TO on;
SET
severalnines_v11=#  explain SELECT count(1),cust_country FROM customers GROUP BY 2;
                                 QUERY PLAN
----------------------------------------------------------------------------
 Append  (cost=1.01..22.67 rows=203 width=40)
   ->  HashAggregate  (cost=1.01..1.02 rows=1 width=40)
         Group Key: customer_ind.cust_country
         ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=32)
   ->  HashAggregate  (cost=1.00..1.01 rows=1 width=40)
         Group Key: customer_jap.cust_country
         ->  Seq Scan on customer_jap  (cost=0.00..1.00 rows=1 width=32)
   ->  HashAggregate  (cost=16.60..18.60 rows=200 width=40)
         Group Key: customer_usa.cust_country
         ->  Seq Scan on customer_usa  (cost=0.00..14.40 rows=440 width=32)
   ->  HashAggregate  (cost=1.00..1.01 rows=1 width=40)
         Group Key: customers_def.cust_country
         ->  Seq Scan on customers_def  (cost=0.00..1.00 rows=1 width=32)
(13 rows)

Questo è sicuramente più veloce in quanto include l'elaborazione dell'aggregazione parallela e la scansione per partizione.

La query del catalogo può essere utilizzata per conoscere tutte le tabelle delle partizioni principali.

SELECT relname FROM pg_class WHERE oid in (select partrelid FROM  pg_partitioned_table);

Breve matrice delle funzioni di partizione

Funzioni di partizionamento v11 v10
Partizione predefinita SI NO
Ereditarietà di tabelle straniere SI NO
Partizionamento tramite chiave hash SI NO
Supporto per PK e FK SI NO
AGGIORNAMENTO su una chiave di partizione SI NO
Inexe automatizzati su CT SI NO
Trigger automatici su CT SI NO
Sfoltimento della partizione del tempo di esecuzione SI NO
Partizione saggia Partecipa SI NO
Eliminazione dinamica della partizione SI NO

E poi?

Rendimento del partizionamento

Questa è una delle aree di lavoro più attive ora nella comunità di PostgreSQL. PostgreSQL versione 12 sarà incluso in un pacchetto con ancora più miglioramenti delle prestazioni nello spazio di partizionamento. La versione 12 dovrebbe essere rilasciata a novembre 2019.