web-dev-qa-db-fra.com

Vitesse de troncature Postgresql

Nous utilisons Postgresql 9.1.4 comme notre serveur db. J'ai essayé d'accélérer ma suite de tests, j'ai donc regardé un peu le profilage de la base de données pour voir exactement ce qui se passait. Nous utilisons database_cleaner pour tronquer les tables à la fin des tests. OUI Je sais que les transactions sont plus rapides, je ne peux pas les utiliser dans certaines circonstances, donc cela ne m'inquiète pas.

Ce qui me préoccupe, c'est pourquoi TRUNCATION prend si longtemps (plus longtemps que l'utilisation de DELETE) et pourquoi cela prend ENCORE PLUS DE TEMPS sur mon serveur CI.

À l'heure actuelle, localement (sur un Macbook Air), une suite de tests complète prend 28 minutes. En suivant les journaux, chaque fois que nous tronquons des tables ... c'est-à-dire:

TRUNCATE TABLE table1, table2  -- ... etc

il faut plus de 1 seconde pour effectuer la troncature. La mise à jour des journaux sur notre serveur CI (Ubuntu 10.04 LTS), prend 8 secondes pour tronquer les tables et une construction prend 84 minutes.

Lorsque je suis passé au :deletion stratégie, ma version locale a pris 20 minutes et le serveur CI est tombé à 44 minutes. C'est une différence significative et je suis vraiment époustouflé de savoir pourquoi cela pourrait être. J'ai régléle DB sur le serveur CI, il a 16 Go de RAM système, 4 Go de shared_buffers ... et un SSD. Toutes les bonnes choses. Comment est-ce possible:

a. que c'est SO beaucoup plus lent que mon Macbook Air avec 2 Go de RAM)
b. que TRUNCATION est tellement plus lent que DELETE lorsque le postgresql docsindiquer explicitement qu'il devrait être beaucoup plus rapide.

Des pensées?

61
brad

Cela est revenu plusieurs fois récemment, à la fois sur SO et sur les listes de diffusion de PostgreSQL.

Le TL; DR pour vos deux derniers points:

(a) Les plus gros buffers partagés peuvent expliquer pourquoi TRUNCATE est plus lent sur le serveur CI. Une configuration fsync différente ou l'utilisation de supports de rotation au lieu de disques SSD pourraient également être en cause.

(b) TRUNCATE a un coût fixe, mais pas nécessairement plus lent que DELETE, en plus il fait plus de travail. Voir l'explication détaillée qui suit.

MISE À JOUR: A discussion importante sur pgsql-performance est née de ce post. Voir ce fil .

MISE À JOUR 2: Des améliorations ont été ajoutées à 9.2beta3 qui devraient aider à cela, voir ce post .

Explication détaillée de TRUNCATE vs DELETE FROM:

Bien que je ne sois pas un expert du sujet, je crois comprendre que TRUNCATE a un coût par table presque fixe, tandis que DELETE est au moins O(n) for n lignes; pire s'il y a des clés étrangères référençant la table en cours de suppression.

J'ai toujours supposé que le coût fixe d'un TRUNCATE était inférieur au coût d'un DELETE sur une table presque vide, mais ce n'est pas vrai du tout.

TRUNCATE table; Fait plus que DELETE FROM table;

L'état de la base de données après un TRUNCATE table Est sensiblement le même que si vous exécutiez à la place:

  • DELETE FROM table;
  • VACCUUM (FULL, ANALYZE) table; (9.0+ uniquement, voir note de bas de page)

... bien sûr, TRUNCATE n'atteint pas réellement ses effets avec un DELETE et un VACUUM.

Le fait est que DELETE et TRUNCATE font des choses différentes, donc vous ne comparez pas seulement deux commandes avec des résultats identiques.

Un DELETE FROM table; Permet aux lignes mortes et aux ballonnements de rester, permet aux index de transporter des entrées mortes, ne met pas à jour les statistiques de table utilisées par le planificateur de requêtes, etc.

Un TRUNCATE vous donne une table et des index complètement nouveaux comme s'ils étaient juste CREATEed. C'est comme si vous aviez supprimé tous les enregistrements, réindexé la table et fait un VACUUM FULL.

Si vous ne vous souciez pas s'il reste de la saleté dans le tableau parce que vous êtes sur le point de le remplir à nouveau, il vaut mieux utiliser DELETE FROM table;.

Comme vous n'exécutez pas VACUUM, vous constaterez que les lignes mortes et les entrées d'index s'accumulent sous forme de ballonnements qui doivent être analysés puis ignorés; cela ralentit toutes vos requêtes. Si vos tests ne créent pas et ne suppriment pas vraiment autant de données, vous ne les remarquerez peut-être pas et vous pourrez toujours faire un VACUUM ou deux à mi-chemin de votre test si vous le faites. Mieux, laissez les paramètres agressifs du vide automatique assurer que le vide automatique le fait pour vous en arrière-plan.

Vous pouvez toujours TRUNCATE toutes vos tables après les exécutions de la suite de tests entier pour vous assurer qu'aucun effet ne se développe sur plusieurs exécutions. Sur 9.0 et plus récent, VACUUM (FULL, ANALYZE); globalement sur la table est au moins aussi bon sinon meilleur, et c'est beaucoup plus facile.

IIRC Pg a quelques optimisations qui signifient qu'il peut remarquer quand votre transaction est la seule qui peut voir le tableau et marquer immédiatement les blocs comme libres de toute façon. Lors des tests, lorsque j'ai voulu créer un ballonnement, j'ai dû avoir plus d'une connexion simultanée pour le faire. Je ne compterais pas là-dessus, cependant.

DELETE FROM table; Est très bon marché pour les petites tables sans références f/k

Pour DELETE tous les enregistrements d'une table sans référence de clé étrangère, tous les Pg doivent effectuer une analyse séquentielle de la table et définir le xmax des tuples rencontrés. Il s'agit d'une opération très bon marché - essentiellement une lecture linéaire et une écriture semi-linéaire. AFAIK il n'a pas à toucher les index; ils continuent à pointer vers les tuples morts jusqu'à ce qu'ils soient nettoyés par un VACUUM ultérieur qui marque également les blocs de la table contenant uniquement les tuples morts comme libres.

DELETE ne coûte cher que s'il y a lots d'enregistrements, s'il y a beaucoup de références de clés étrangères qui doivent être vérifiées, ou si vous comptez les VACUUM (FULL, ANALYZE) table; suivants nécessaire pour faire correspondre les effets de TRUNCATE dans le coût de votre DELETE.

Dans mes tests ici, un DELETE FROM table; Était généralement 4x plus rapide que TRUNCATE à 0,5 ms contre 2 ms. C'est une base de données de test sur un SSD, fonctionnant avec fsync=off Car je m'en fiche si je perds toutes ces données. Bien sûr, DELETE FROM table; Ne fait pas tout le même travail, et si je fais un suivi avec un VACUUM (FULL, ANALYZE) table; c'est un 21ms beaucoup plus cher, donc le DELETE n'est qu'une victoire si je n'ai pas vraiment besoin de la table vierge.

TRUNCATE table; Fait beaucoup plus de travaux à frais fixes et d'entretien ménager que DELETE

En revanche, un TRUNCATE doit faire beaucoup de travail. Il doit allouer de nouveaux fichiers pour la table, sa table TOAST le cas échéant et chaque index de la table. Les en-têtes doivent être écrits dans ces fichiers et les catalogues système peuvent également avoir besoin d'être mis à jour (pas sûr sur ce point, je n'ai pas vérifié) Il doit ensuite remplacer les anciens fichiers par les nouveaux ou supprimer les anciens, et doit s'assurer que le système de fichiers a rattrapé les changements avec une opération de synchronisation - fsync () ou similaire - qui vide généralement tous les tampons sur le disque . Je ne sais pas si la synchronisation est ignorée si vous utilisez l'option (consommation de données) fsync=off.

J'ai appris récemment que TRUNCATE doit également vider tous les tampons de PostgreSQL liés à l'ancienne table. Cela peut prendre un temps non négligeable avec un énorme shared_buffers. Je soupçonne que c'est pourquoi c'est plus lent sur votre serveur CI.

Le solde

Quoi qu'il en soit, vous pouvez voir qu'un TRUNCATE d'une table qui a une table TOAST associée (la plupart le font) et plusieurs index peut prendre quelques instants. Pas long, mais plus long qu'un DELETE d'une table presque vide.

Par conséquent, vous feriez mieux de faire un DELETE FROM table;.

-

Remarque: sur les bases de données antérieures à 9.0, CLUSTER table_id_seq ON table; ANALYZE table; Ou VACUUM FULL ANALYZE table; REINDEX table; Serait un équivalent plus proche de TRUNCATE. L'impl VACUUM FULL Est devenu bien meilleur en 9.0.

140
Craig Ringer

Brad, juste pour te le faire savoir. J'ai examiné assez profondément une question très similaire.

Question connexe: tableaux avec quelques lignes - TRUNCATE le moyen le plus rapide de les vider et de réinitialiser les séquences jointes?

Veuillez également consulter ce problème et cette demande d'extraction:

https://github.com/bmabey/database_cleaner/issues/126

https://github.com/bmabey/database_cleaner/pull/127

Aussi ce fil: http://archives.postgresql.org/pgsql-performance/2012-07/msg00047.php

Je suis désolé d'avoir écrit ceci comme réponse, mais je n'ai trouvé aucun lien de commentaire, peut-être parce qu'il y a déjà trop de commentaires.

5
Stanislav Pankevich

Quelques approches alternatives à considérer:

  • Créez une base de données vide contenant des données statiques de "fixture" et exécutez les tests. Lorsque vous avez terminé, déposez simplement la base de données, qui devrait être rapide.
  • Créez une nouvelle table appelée "test_ids_to_delete" qui contient des colonnes pour les noms de table et les ID de clé primaire. Mettez à jour votre logique de suppression pour insérer à la place les identifiants/noms de table dans cette table, ce qui sera beaucoup plus rapide que l'exécution de suppressions. Ensuite, écrivez un script à exécuter "hors ligne" pour supprimer réellement les données, soit après la fin d'un test complet, soit pendant la nuit.

La première est une approche de "salle blanche", tandis que la seconde signifie que certaines données de test persisteront plus longtemps dans la base de données. L'approche "sale" avec les suppressions hors ligne est ce que j'utilise pour une suite de tests avec environ 20 000 tests. Oui, il y a parfois des problèmes en raison de la présence de données de test "supplémentaires" dans la base de données de développement, mais parfois. Mais parfois, cette "saleté" nous a aidés à trouver et à corriger un bug parce que le "désordre" simulait mieux une situation réelle, d'une manière que l'approche en salle blanche ne fera jamais.

0
Mark Stosberg

J'ai rencontré un problème similaire récemment, à savoir:

  1. Le temps d'exécution de la suite de tests qui utilisait DatabaseCleaner variait considérablement d'un système à l'autre avec un matériel comparable,
  2. Modification de la stratégie DatabaseCleaner en :deletion a fourni une amélioration d'environ 10 fois.

La cause principale de la lenteur était un système de fichiers avec journalisation (ext4) utilisé pour le stockage de la base de données. Au cours de l'opération TRUNCATE, le démon de journalisation (jbd2) utilisait ~ 90% de la capacité du disque IO. Je ne sais pas s'il s'agit d'un bogue, d'un cas Edge ou d'un comportement réellement normal dans ces circonstances. explique cependant pourquoi TRUNCATE était beaucoup plus lent que DELETE - il générait beaucoup plus d'écritures sur disque. Comme je ne voulais pas réellement utiliser DELETE, j'ai eu recours au paramètre fsync=off et c'était suffisant pour atténuer ce problème (la sécurité des données n'était pas importante dans ce cas).

0
Maksym Kammerer