PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment tirer parti des nouvelles fonctionnalités de partitionnement dans PostgreSQL 11

Qu'est-ce que le partitionnement ?

Le partitionnement divise les tables volumineuses en éléments plus petits, ce qui permet d'augmenter les performances des requêtes, de faciliter les tâches de maintenance, d'améliorer l'efficacité de l'archivage des données et d'accélérer les sauvegardes de bases de données. Vous pouvez en savoir plus sur le partitionnement PostgreSQL dans notre blog "A Guide to Partitioning Data In PostgreSQL".

Avec la récente version de PostgreSQL 11, il existe de nombreuses nouvelles fonctionnalités de partitionnement étonnantes. Les détails de ces nouvelles fonctionnalités de partitionnement seront couverts dans ce blog avec quelques exemples de code.

Mise à jour des clés de partition

Avant PostgreSQL 11, l'instruction Update qui modifie la valeur de la clé de partition était restreinte et non autorisée. C'est désormais possible dans la nouvelle version. L'instruction de mise à jour peut modifier la valeur de la clé de partition ; il déplace en fait les lignes vers la table de partition correcte. Sous le capot, il exécute essentiellement DELETE FROM l'ancienne partition et INSERT dans la nouvelle partition ( DELETE + INSERT).

Très bien, testons cela. Créez une table et vérifiez comment la mise à jour fonctionne sur la clé de partition.

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)

Attention :UPDATE générera une erreur s'il n'y a pas de table de partition par défaut et que les valeurs mises à jour ne correspondent pas aux critères de partition dans aucune table enfant.

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]

Création d'une partition par défaut

La fonctionnalité de partition DEFAULT de PostgreSQL 11 stocke les tuples qui ne correspondent à aucune autre partition. Avant PostgreSQL 11, ces lignes produisaient une erreur. Une ligne qui n'est mappée sur aucune table de partition serait insérée dans la partition par défaut.

Exemple de laboratoire :le code de pays "États-Unis" n'a pas été défini dans la table de partition ci-dessous, mais il est quand même inséré avec succès dans la table par défaut.

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

Avertissement :la partition par défaut empêchera tout ajout de nouvelle partition si cette valeur de partition existe dans la table par défaut. Dans ce cas, "USA" existait dans la partition par défaut, donc cela ne fonctionnera pas comme ci-dessous.

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 partition DEFAULT ne peut pas être spécifiée pour la table partitionnée HASH. Il ne peut y avoir plus d'une table DEFAULT pour la table de partition.

Partitionnement par hachage

Il s'agit d'un nouveau mécanisme de partition, si vous ne pouvez pas décider d'une partition de plage ou de liste (car vous n'êtes pas sûr de la taille du seau). Le partitionnement par hachage résout ce problème de distribution de données.

La table est partitionnée en spécifiant un module et un reste pour chaque partition. Chaque partition contiendra les lignes pour lesquelles la valeur de hachage de la clé de partition divisée par le module spécifié produira le reste spécifié. La fonction HASH garantit que les lignes seront réparties de manière presque égale dans toute la table de partition.

Pour commencer, vous devez décider du nombre de numéros de la table de partition requis et, en conséquence, le module et le reste peuvent être définis ; si le module était de 4, le reste ne peut être que de [0-3].

[Module - Nombre de tables | Reste - Quelle valeur de reste va à quel seau ]

Comment configurer une partition de hachage

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

Insérez 50 000 enregistrements dans la table parent :

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

et voyez comment il a distribué les enregistrements uniformément dans la table enfant ...

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)

Nous ne pouvons pas modifier le nombre de partitions spécifié par `Modulus` plus tôt, vous devez donc planifier bien avant les exigences relatives au nombre de tables de partition.

Une erreur se produira lorsque vous essayez d'ajouter une nouvelle partition avec un reste différent.

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

Le partitionnement par hachage peut fonctionner sur n'importe quel type de données et il peut également fonctionner pour le type UUID. Il est toujours recommandé que le nombre de tables soit une puissance de 2, et il n'est pas non plus obligatoire d'utiliser le même module lors de la création de la table ; cela aidera à créer la table de partition plus tard, si nécessaire.

Cette implémentation rendrait également le vide plus rapide et peut activer la jointure par partition.

Prise en charge des clés étrangères

Avant PostgreSQL 11, la clé étrangère dans la table de partition n'était pas prise en charge. Les clés étrangères sont maintenant possibles dans la table de partition et voici comment...

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

Création automatique d'index sur les tables enfants

Dans les versions précédentes de PostgreSQL, il s'agissait d'un effort manuel pour créer un index sur chaque table de partition. Dans PostgreSQL version 11, c'est assez pratique pour les utilisateurs. Une fois l'index créé sur la table maître, il créera automatiquement l'index avec la même configuration sur toutes les partitions enfants existantes et s'occupera également de toutes les futures tables de partition.

Index créé sur la table principale

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

Il a automatiquement créé l'index sur toutes les tables enfants comme ci-dessous. (Vérifier avec la table du catalogue)

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'index ne peut être créé que sur une table maître, il ne peut pas être sur une table enfant. Les index générés automatiquement ne peuvent pas être supprimés individuellement.

Création automatique de déclencheurs sur les tables enfants

Une fois le déclencheur créé sur la table maître, il créera automatiquement le déclencheur sur toutes les tables enfants (ce comportement est similaire à celui observé pour l'index).

Capable de créer un index unique

Dans la version 11, des index uniques peuvent être ajoutés à la table maître, ce qui créera la contrainte unique sur toutes les tables enfants existantes et les futures tables de partition.

Créons une table maître avec des contraintes uniques.

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

La contrainte unique a été créée automatiquement sur la table enfant comme ci-dessous.

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)

Attention :Une contrainte d'unicité sur la table parent ne garantit pas réellement l'unicité sur l'ensemble de la hiérarchie de partitionnement. Ce n'est pas une contrainte globale, c'est uniquement local.

Téléchargez le livre blanc aujourd'hui PostgreSQL Management &Automation with ClusterControlDécouvrez ce que vous devez savoir pour déployer, surveiller, gérer et faire évoluer PostgreSQLTélécharger le livre blanc

Performance des requêtes plus rapide

Élagage dynamique des partitions

Dans PostgreSQL 11, la recherche binaire permet une identification plus rapide des tables enfants requises, qu'elles soient partitionnées LIST ou RANGE. La fonction de hachage trouve la partition correspondante pour la partition HASH. En fait, il élimine dynamiquement les tables de partition qui ne sont pas nécessaires et améliore les performances de la requête.

L'élagage dynamique des partitions peut être contrôlé par le paramètre `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'autre implémentation géniale est comme ça.

Élagage des partitions au moment de l'exécution

Dans les versions de PostgreSQL antérieures à 11, l'élagage des partitions ne peut se produire qu'au moment du plan ; le planificateur nécessite une valeur de clé de partition pour identifier la bonne partition. Ce comportement est corrigé dans PostgreSQL 11, car le planificateur de temps d'exécution saurait quelle valeur est fournie et sur la base de cette sélection/élimination de partition est possible et s'exécuterait beaucoup plus rapidement. Le cas d'utilisation peut être une requête qui utilise un paramètre (instruction préparée) OU une sous-requête qui fournit la valeur en tant que paramètre.

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)

Dans le plan d'explication ci-dessus, nous pouvons voir qu'au moment de l'exécution, le planificateur a identifié à la volée la table de partition correcte en fonction de la valeur du paramètre, et s'est exécuté beaucoup plus rapidement et n'a pas passé de temps à analyser/boucler sur une autre table de partition (voir jamais section exécutée dans le plan d'explication ci-dessus). C'est très puissant et a lancé une nouvelle ère d'amélioration des performances dans le partitionnement.

Partition Wise Aggregate

Paramètre :enable_partitionwise_aggregate

Si la clé de partition correspond à la clé de regroupement, chaque partition produira un ensemble discret de groupes au lieu d'analyser toute la partition à la fois. Il fera l'agrégat parallèle pour chaque partition et lors du résultat final, il concatènera tous les résultats.

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)

C'est sûrement plus rapide car il inclut le traitement d'agrégation parallèle et l'analyse par partition.

La requête de catalogue peut être utilisée pour connaître toutes les tables de partition parent.

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

Brève matrice des fonctionnalités de partition

Fonctionnalités de partitionnement v11 v10
Partition par défaut OUI NON
Héritage de table étrangère OUI NON
Partitionnement par clé de hachage OUI NON
Prise en charge de PK et FK OUI NON
UPDATE sur une clé de partition OUI NON
Inexes automatisés sur CT OUI NON
Déclencheurs automatisés sur CT OUI NON
Élagage des partitions au moment de l'exécution OUI NON
Joindre par partition OUI NON
Élagage de partition dynamique OUI NON

Et ensuite ?

Performances de partitionnement

C'est l'un des domaines de travail les plus actifs actuellement dans la communauté PostgreSQL. PostgreSQL Version 12 sera packagé avec encore plus d'améliorations des performances dans l'espace de partitionnement. La version 12 devrait sortir en novembre 2019.