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?
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 CREATE
ed. 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.
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.
Quelques approches alternatives à considérer:
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.
J'ai rencontré un problème similaire récemment, à savoir:
: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).