L'ajout d'une colonne nullable à la table coûte plus de 10 minutes

11

J'ai des problèmes pour ajouter une nouvelle colonne sur une table.
J'ai essayé de l'exécuter plusieurs fois, mais après plus de 10 minutes de fonctionnement, j'ai décidé d'annuler la requête en raison du temps de verrouillage.

ALTER TABLE mytable ADD mycolumn VARCHAR(50);

Informations utiles:

  • Version PostgreSQL: 9.1
  • Nombre de lignes: ~ 250 K
  • Nombre de colonnes: 38
  • Nombre de colonnes nullables: 32
  • Nombre de contraintes: 5 (1 PK, 3 FK, 1 UNIQUE)
  • Nombre d'index: 1
  • Type de système d'exploitation: Debian Squeeze 64

J'ai trouvé des informations intéressantes sur la façon dont PostgreSQL gère les colonnes nullables (via HeapTupleHeader).

Ma première supposition est que parce que cette table a déjà 32 colonnes nullables avec 8 bits MAXALIGN, HeapTupleHeader a une longueur de 4 octets (non vérifié, et je ne sais pas comment le faire).

Ainsi, l'ajout d'une nouvelle colonne nullable peut nécessiter une mise à jour de HeapTupleHeader sur chaque ligne pour ajouter un nouveau 8 bits MAXALIGN, ce qui pourrait entraîner des problèmes de performances.

J'ai donc essayé de modifier l'une des colonnes nullable (qui n'est pas vraiment nullable en réalité) afin de réduire à 31 le nombre de colonnes nullable, pour vérifier si ma supposition pouvait être vraie.

ALTER TABLE mytable ALTER myothercolumn SET NOT NULL;

Malheureusement, cette modification prend également très longtemps, plus de 5 minutes, je l'ai donc également abandonnée.

Avez-vous une idée de ce qui pourrait entraîner ce coût de performance?

Matthieu Verrecchia
la source
1
Eh bien, je peux vous en dire une partie: la modification d'un type de colonne par un autre type non compatible binaire crée en fait une nouvelle colonne, copie les données et définit l'ancienne colonne comme supprimée. Cependant, SET NOT NULLne modifie pas le type, il ajoute simplement une contrainte - mais la contrainte doit être vérifiée par rapport à la table, et cela nécessite une analyse complète de la table. 9.4 améliore certains de ces cas en prenant des verrous plus faibles, mais il est toujours assez lourd.
Craig Ringer
1
Avant de soupçonner qu'il fonctionne lentement, vous devez vous assurer que ALTER TABLE n'attend pas seulement un verrou. Mentionnez-le dans la question si vous avez coché.
Daniel Vérité
Merci Craig et Daniel. Lorsque j'exécute la commande alter, elle apparaît dans pg_stat_activity avec l'attente "true", je suppose que cela signifie qu'elle attend un verrou!? C'est le bon moyen de vérifier? Soit dit en passant, avant d'exécuter cette modification, tout va bien, mais quelques secondes après le début, le nombre de verrous augmente
Essayez la requête sur wiki.postgresql.org/wiki/Lock_dependency_information pour une meilleure vue. Soit vous avez des transactions persistantes qui oublient de valider, soit une activité intense avec cette table qui la tient toujours occupée.
Daniel Vérité
Pourrait être un meilleur ajustement à dba.SE.
Erwin Brandstetter

Réponses:

8

Il y a quelques malentendus ici:

Le bitmap nul ne fait pas partie de l'en-tête de tuple de tas. Par documentation:

Il y a un en-tête de taille fixe (occupant 23 octets sur la plupart des machines), suivi d'un bitmap nul facultatif ...

Vos 32 colonnes annulables ne sont pas suspectes pour deux raisons:

  • Le bitmap nul est ajouté par ligne , et uniquement s'il existe au moins une NULLvaleur réelle dans la ligne. Les colonnes nulles n'ont pas d'impact direct, seules les NULLvaleurs réelles le font. Si le bitmap nul est alloué, il est toujours alloué complètement (tout ou rien). La taille réelle du bitmap nul est de 1 bit par colonne, arrondie à l'octet suivant . Par code souce actuel:

    #define BITMAPLEN(NATTS) (((int)(NATTS) + 7) / 8)
  • Le bitmap nul est alloué après l'en-tête de tuple de tas et suivi d'un OID facultatif, puis de données de ligne. Le début d'un OID ou d'une ligne de données est indiqué par t_hoffdans l'en-tête. Par code source de commentaire :

    Notez que t_hoff doit être un multiple de MAXALIGN.

  • Il y a un octet libre après l'en-tête de tuple de tas, qui occupe 23 octets. Ainsi, le bitmap nul pour les lignes jusqu'à 8 colonnes est effectivement sans frais supplémentaires. Avec la 9e colonne du tableau, t_hoffun autre MAXALIGNoctet (généralement 8) est avancé pour fournir 64 autres colonnes. La prochaine frontière serait donc à 72 colonnes.

Pour afficher les informations de contrôle d'un cluster de base de données PostgreSQL (incl. MAXALIGN), Exemple pour une installation typique de Postgres 9.3 sur une machine Debian:

    sudo /usr/lib/postgresql/9.3/bin/pg_controldata /var/lib/postgresql/9.3/main

J'ai mis à jour les instructions dans la réponse connexe que vous avez citée .

Tout cela mis à part, même si votre ALTER TABLEinstruction déclenche une réécriture de table entière (ce qu'elle fait probablement, en changeant un type de données), 250 Ko ne sont vraiment pas beaucoup et seraient une question de secondes sur n'importe quelle machine à mi-chemin décente (à moins que les lignes soient anormalement grandes) . 10 minutes ou plus indiquent un problème complètement différent. Votre déclaration attend très probablement un verrou sur la table.

Le nombre croissant d'entrées dans pg_stat_activitysignifie des transactions plus ouvertes - indique un accès simultané sur la table (le plus probable) qui doit attendre la fin de l'opération.

Quelques clichés dans le noir

Vérifiez l'éventuel ballonnement de la table, essayez une solution douce VACUUM mytableou plus agressive VACUUM FULL mytable- qui pourrait rencontrer les mêmes problèmes de concurrence, car ce formulaire acquiert également un verrou exclusif. Vous pouvez essayer pg_repack à la place ...

Je commencerais par inspecter les problèmes possibles avec les index, les déclencheurs, la clé étrangère ou d'autres contraintes, en particulier ceux impliquant la colonne. En particulier, un index corrompu pourrait être impliqué? Essayez-les REINDEX TABLE mytable;ou DROPtous et ré-ajoutez-les après ALTER TABLE dans la même transaction .

Essayez d'exécuter la commande dans la nuit ou chaque fois qu'il n'y a pas beaucoup de charge.

Une méthode de force brute serait d'arrêter l'accès au serveur, puis de réessayer:

Sans pouvoir le cerner, la mise à niveau vers la version actuelle ou la prochaine 9.4 en particulier pourrait aider. Plusieurs améliorations ont été apportées aux grandes tables et aux détails de verrouillage. Mais s'il y a quelque chose de cassé dans votre base de données, vous devriez probablement le découvrir en premier.

Erwin Brandstetter
la source
2
C'est presque certainement des verrous. Mais, à titre de test, vous pouvez toujours créer une copie de la table et essayer de la modifier. Si cela ne prend pas très longtemps, vous savez que ce n'est pas la modification réelle qui est le problème.
Merci pour les explications Erwin. Je pense que vous avez raison, il semble que ce soit un problème de verrouillage. Quand je vérifie pg_stat_activity, je peux voir que mon ALTER a un "attente" vrai. Ce que je ne peux pas comprendre, c'est pourquoi ALTER ne peut pas obtenir le verrou sur la table, car même lorsque je ne trouve aucune requête en cours d'exécution, il semble qu'il ne puisse pas l'obtenir. Mais dès que mon ALTER commence à s'exécuter, toutes les autres requêtes attendent qu'il se termine. Ainsi, l'activité semble indiquer que ALTER verrouille toutes les autres requêtes, mais indique également que ALTER n'a pas obtenu le verrou. Je pense qu'il y a quelque chose que je ne comprends pas bien!?
@MatthieuVerrecchia: Avez-vous essayé le test suggéré par Richard?
Erwin Brandstetter
1
Je viens de cloner ma table dans une nouvelle (avec pg_dump -> pg_sql). La nouvelle colonne est correctement ajoutée en 50 ms, ce qui confirme le problème de verrouillage. Au fait, je ne comprends toujours pas pourquoi ALTER ne peut pas se verrouiller avec une activité de base de données vraiment standard.
1
@ErwinBrandstetter J'ai suivi vos suggestions et essayé un VIDE, puis un REINDEX. Le REINDEX bloquait également, car il n'a pas pu non plus acquérir le verrou. Après quelques investigations, le problème était plus simple que nous ne le pensions. Il restait une semaine <IDLE> avec une transaction ouverte Le problème est résolu, merci pour tout, les informations ont été très utiles.