0 Travaux pratiques
0.1 Travaux pratiques
La version en ligne des solutions de ces TP est disponible sur https://dali.bo/v1_solutions.
0.1.1 Partitionnement
But : Mettre en place le partitionnement déclaratif
Nous travaillons sur la base cave. Si vous ne l’avez pas déjà, la base cave (dump de 2,6 Mo, pour 71 Mo sur le disque au final) peut être téléchargée et restaurée ainsi :
Nous allons partitionner la table stock sur l’année.
Pour nous simplifier la vie, nous allons limiter le nombre d’années dans stock (cela nous évitera la création de 50 partitions) :
Nous n’avons maintenant que des bouteilles des années 2001 à 2005.
- Renommer
stockenstock_old.- Créer une table partitionnée
stockvide, sans index pour le moment.
- Créer les partitions de
stock, avec la contrainte d’année :stock_2001àstock_2005.
- Insérer tous les enregistrements venant de l’ancienne table
stock.
- Passer les statistiques pour être sûr des plans à partir de maintenant (nous avons modifié beaucoup d’objets).
- Vérifier la présence d’enregistrements dans
stock_2001(syntaxeSELECT ONLY).- Vérifier qu’il n’y en a aucun dans
stock.
- Vérifier qu’une requête sur
stocksur 2002 ne parcourt qu’une seule partition.
- Remettre en place les index présents dans la table
stockoriginale.- Il se peut que d’autres index ne servent à rien (ils ne seront dans ce cas pas présents dans la correction).
- Quel est le plan pour la récupération du stock des bouteilles du
vin_id1725, année 2003 ?
- Essayer de changer l’année de ce même enregistrement de
stock(la même que la précédente). Pourquoi cela échoue-t-il ?
- Supprimer les enregistrements de 2004 pour
vin_id= 1725.- Retenter la mise à jour.
- Pour vider complètement le stock de 2001, supprimer la partition
stock_2001.
- Tenter d’ajouter au stock une centaine de bouteilles de 2006.
- Pourquoi cela échoue-t-il ?
- Créer une partition par défaut pour recevoir de tels enregistrements.
- Retenter l’ajout.
- Tenter de créer la partition pour l’année 2006. Pourquoi cela échoue-t-il ?
- Pour créer la partition sur 2006, au sein d’une seule transaction :
- détacher la partition par défaut ;
- y déplacer les enregistrements mentionnés ;
- ré-attacher la partition par défaut.
0.1.2 Partitionner pendant l’activité
But : Mettre en place le partitionnement déclaratif sur une base en cours d’activité
0.1.2.1 Préparation
Créer une base pgbench vierge, de taille 10 ou plus.
NB : Pour le TP, la base sera d’échelle 10 (environ 168 Mo). Des échelles 100 ou 1000 seraient plus réalistes.
Dans une fenêtre en arrière-plan, laisser tourner un processus
pgbenchavec une activité la plus soutenue possible. Il ne doit pas tomber en erreur pendant que les tables vont être partitionnées ! Certaines opérations vont poser des verrous, le but va être de les réduire au maximum.
Pour éviter un « empilement des verrous » et ne pas bloquer trop longtemps les opérations, faire en sorte que la transaction échoue si l’obtention d’un verrou dure plus de 10 s.
0.1.2.2 Partitionnement par hash
Pour partitionner la table
pgbench_accountspar hash sur la colonneaidsans que le traitement pgbench tombe en erreur, préparer un script avec, dans une transaction :
- la création d’une table partitionnée par hash en 3 partitions au moins ;
- le transfert des données depuis
pgbench_accounts;- la substitution de la table partitionnée à la table originale.
Tester et exécuter.
Supprimer l’ancienne table
pgbench_accounts_old.
0.1.2.3 Partitionnement par valeur
pgbench doit continuer ses opérations en tâche de fond.
La table
pgbench_historyse remplit avec le temps. Elle doit être partitionnée par date (champmtime). Pour le TP, on fera 2 partitions d’une minute, et une partition par défaut. La table actuelle doit devenir une partition de la nouvelle table partitionnée.
- Écrire un script qui, dans une seule transaction, fait tout cela et substitue la table partitionnée à la table d’origine.
NB : Pour éviter de coder des dates en dur, il est possible, avec
psql, d’utiliser une variable :
Exécuter le script, attendre que les données s’insèrent dans les nouvelles partitions.
0.1.2.4 Purge
- Continuer de laisser tourner
pgbenchen arrière-plan.- Détacher et détruire la partition avec les données les plus anciennes.
0.1.2.5 Contraintes entre tables partitionnées
- Ajouter une clé étrangère entre
pgbench_accountsetpgbench_history. Voir les contraintes créées.
Si vous n’avez pas déjà eu un problème à cause du
statement_timeout, dropper la contrainte et recommencer avec une valeur plus basse. Comment contourner ?
0.1.2.6 Index global
On veut créer un index sur
pgbench_history (aid).Pour ne pas gêner les écritures, il faudra le faire de manière concurrente. Créer l’index de manière concurrente sur chaque partition, puis sur la table partitionnée.
0.2 Travaux pratiques (solutions)
0.2.1 Partitionnement
But : Mettre en place le partitionnement déclaratif
Pour nous simplifier la vie, nous allons limiter le nombre d’années dans stock (cela nous évitera la création de 50 partitions).
Nous n’avons maintenant que des bouteilles des années 2001 à 2005.
- Renommer
stockenstock_old.- Créer une table partitionnée
stockvide, sans index pour le moment.
- Créer les partitions de
stock, avec la contrainte d’année :stock_2001àstock_2005.
- Insérer tous les enregistrements venant de l’ancienne table
stock.
- Passer les statistiques pour être sûr des plans à partir de maintenant (nous avons modifié beaucoup d’objets).
- Vérifier la présence d’enregistrements dans
stock_2001(syntaxeSELECT ONLY).- Vérifier qu’il n’y en a aucun dans
stock.
- Vérifier qu’une requête sur
stocksur 2002 ne parcourt qu’une seule partition.
- Remettre en place les index présents dans la table
stockoriginale.- Il se peut que d’autres index ne servent à rien (ils ne seront dans ce cas pas présents dans la correction).
Les autres index ne servent à rien sur les partitions : idx_stock_annee est évidemment inutile, mais idx_stock_vin_annee aussi, puisqu’il est inclus dans l’index unique que nous venons de créer.
- Quel est le plan pour la récupération du stock des bouteilles du
vin_id1725, année 2003 ?
- Essayer de changer l’année de ce même enregistrement de
stock(la même que la précédente). Pourquoi cela échoue-t-il ?
C’est une violation de contrainte unique, qui est une erreur normale : nous avons déjà un enregistrement de stock pour ce vin pour l’année 2004.
- Supprimer les enregistrements de 2004 pour
vin_id= 1725.- Retenter la mise à jour.
- Pour vider complètement le stock de 2001, supprimer la partition
stock_2001.
- Tenter d’ajouter au stock une centaine de bouteilles de 2006.
- Pourquoi cela échoue-t-il ?
Il n’existe pas de partition définie pour l’année 2006, cela échoue donc.
- Créer une partition par défaut pour recevoir de tels enregistrements.
- Retenter l’ajout.
- Tenter de créer la partition pour l’année 2006. Pourquoi cela échoue-t-il ?
Cela échoue car des enregistrements présents dans la partition par défaut répondent à cette nouvelle contrainte de partitionnement.
- Pour créer la partition sur 2006, au sein d’une seule transaction :
- détacher la partition par défaut ;
- y déplacer les enregistrements mentionnés ;
- ré-attacher la partition par défaut.
0.2.2 Partitionner pendant l’activité
But : Mettre en place le partitionnement déclaratif sur une base en cours d’activité
0.2.2.1 Préparation
Créer une base pgbench vierge, de taille 10 ou plus.
Dans une fenêtre en arrière-plan, laisser tourner un processus
pgbenchavec une activité la plus soutenue possible. Il ne doit pas tomber en erreur pendant que les tables vont être partitionnées ! Certaines opérations vont poser des verrous, le but va être de les réduire au maximum.
L’activité est à ajuster en fonction de la puissance de la machine. Laisser l’affichage défiler dans une fenêtre pour bien voir les blocages.
Pour éviter un « empilement des verrous » et ne pas bloquer trop longtemps les opérations, faire en sorte que la transaction échoue si l’obtention d’un verrou dure plus de 10 s.
Un verrou en attente peut bloquer les opérations d’autres transactions venant après. On peut annuler l’opération à partir d’un certain seuil pour éviter ce phénomène :
Cela ne concerne cependant pas les opérations une fois que les verrous sont acquis. On peut garantir qu’un ordre donné ne durera pas plus d’une certaine durée :
En fonction de la rapidité de la machine et des données à déplacer, cette interruption peut être tolérable ou non.
0.2.2.2 Partitionnement par hash
Pour partitionner la table
pgbench_accountspar hash sur la colonneaidsans que le traitement pgbench tombe en erreur, préparer un script avec, dans une transaction :
- la création d’une table partitionnée par hash en 3 partitions au moins ;
- le transfert des données depuis
pgbench_accounts;- la substitution de la table partitionnée à la table originale.
Tester et exécuter.
Le champ aid n’a pas de signification, un partitionnement par hash est adéquat.
Le script peut être le suivant :
À la moindre erreur, la transaction tombe en erreur. Il faudra demander manuellement ROLLBACK.
Si la durée fixée par statement_timeout est dépassée, on aura cette erreur :
Surtout, le traitement pgbench reprend en arrière-plan. On peut alors relancer le script corrigé plus tard.
Si tout se passe bien, un \d+ renvoie ceci :
On peut vérifier rapidement que les valeurs de aid sont bien réparties entre les 3 partitions :
Après la validation du script, on voit apparaître les lignes dans les nouvelles partitions :
Supprimer l’ancienne table
pgbench_accounts_old.
0.2.2.3 Partitionnement par valeur
pgbench doit continuer ses opérations en tâche de fond.
La table
pgbench_historyse remplit avec le temps. Elle doit être partitionnée par date (champmtime). Pour le TP, on fera 2 partitions d’une minute, et une partition par défaut. La table actuelle doit devenir une partition de la nouvelle table partitionnée.
- Écrire un script qui, dans une seule transaction, fait tout cela et substitue la table partitionnée à la table d’origine.
NB : Pour éviter de coder des dates en dur, il est possible, avec
psql, d’utiliser une variable :
La « date frontière » doit être dans le futur (proche). En effet, pgbench va modifier les tables en permanence, on ne sait pas exactement à quel moment la transition aura lieu (et de toute façon on ne maîtrise pas les valeurs de mtime) : il continuera donc à écrire dans l’ancienne table, devenue partition, pendant encore quelques secondes.
Cette date est arbitrairement à 1 minute dans le futur, pour dérouler le script manuellement :
Et on peut réutiliser cette variable ainsi ;
Le script peut être celui-ci :
Exécuter le script, attendre que les données s’insèrent dans les nouvelles partitions.
Pour surveiller le contenu des tables jusqu’à la transition :
Un \d+ doit renvoyer ceci :
0.2.2.4 Purge
- Continuer de laisser tourner
pgbenchen arrière-plan.- Détacher et détruire la partition avec les données les plus anciennes.
0.2.2.5 Contraintes entre tables partitionnées
- Ajouter une clé étrangère entre
pgbench_accountsetpgbench_history. Voir les contraintes créées.
NB : les clés étrangères entre tables partitionnées ne sont pas disponibles avant PostgreSQL 12.
On voit que chaque partition porte un index comme la table mère. La contrainte est portée par chaque partition.
Si vous n’avez pas déjà eu un problème à cause du
statement_timeout, dropper la contrainte et recommencer avec une valeur plus basse. Comment contourner ?
Le statement_timeout peut être un problème :
On peut créer les contraintes séparément sur les tables. Cela permet de ne poser un verrou sur la partition active (sans doute pgbench_history_default) que pendant le strict minimum de temps (les autres partitions de pgbench_history ne sont pas utilisées).
La contrainte au niveau global sera alors posée presque instantanément :
0.2.2.6 Index global
On veut créer un index sur
pgbench_history (aid).Pour ne pas gêner les écritures, il faudra le faire de manière concurrente. Créer l’index de manière concurrente sur chaque partition, puis sur la table partitionnée.
Construire un index de manière concurrente (clause CONCURRENTLY) permet de ne pas bloquer la table en écriture pendant la création de l’index, qui peut être très longue. Mais il n’est pas possible de le faire sur la table partitionnée :
Mais on peut créer l’index sur chaque partition séparément :
S’il y a beaucoup de partitions, on peut générer dynamiquement ces ordres :
Comme lors de toute création concurrente, il faut vérifier que les index sont bien valides : la requête suivante ne doit rien retourner.
Enfin on crée l’index au niveau de la table partitionnée : il réutilise les index existants et sera donc créé presque instantanément :