Optimiser les performances de mise à jour en masse dans PostgreSQL

37

Utiliser PG 9.1 sur Ubuntu 12.04.

Il nous faut actuellement jusqu'à 24 heures pour exécuter un grand nombre d'instructions UPDATE sur une base de données, qui se présente sous la forme:

UPDATE table
SET field1 = constant1, field2 = constant2, ...
WHERE id = constid

(Nous ne faisons que remplacer des champs d'objets identifiés par un ID.) Les valeurs proviennent d'une source de données externe (pas déjà dans le DB dans une table).

Les tables ont des poignées d'indices chacune et aucune contrainte de clé étrangère. Aucun engagement n'est fait jusqu'à la fin.

Il faut 2h pour importer une partie pg_dumpde la base de données. Cela semble être une base de référence que nous devrions raisonnablement cibler.

Mis à part la production d'un programme personnalisé qui reconstruit en quelque sorte un ensemble de données que PostgreSQL peut réimporter, pouvons-nous faire quelque chose pour rapprocher les performances UPDATE en bloc de celles de l'importation? (C’est un domaine que nous pensons être bien géré par les arbres de fusion logarithmiques, mais nous nous demandons s’il est possible de faire quelque chose dans PostgreSQL.)

Quelques idées:

  • abandonner tous les index non-ID et reconstruire ensuite?
  • checkpoint_segments croissants, mais cela aide-t-il réellement à un débit soutenu à long terme?
  • en utilisant les techniques mentionnées ici ? (Charger les nouvelles données sous forme de table, puis "fusionner" les anciennes données sans identifiant)

Fondamentalement, il y a beaucoup de choses à essayer et nous ne savons pas ce qui est le plus efficace ou si nous négligeons d'autres choses. Nous allons passer les prochains jours à expérimenter, mais nous pensions pouvoir le faire ici aussi.

J'ai une charge simultanée sur la table mais elle est en lecture seule.

Yang
la source
Il manque des informations cruciales dans votre question: Votre version de Postgres? D'où viennent les valeurs? Cela ressemble à un fichier en dehors de la base de données, mais veuillez clarifier. Avez-vous une charge simultanée sur la table cible? Si oui, quoi exactement? Ou pouvez-vous vous permettre de laisser tomber et de recréer? Pas de clé étrangère, d'accord - mais y a-t-il d'autres objets dépendants comme des vues? Veuillez modifier votre question avec les informations manquantes. Ne le pressez pas dans un commentaire.
Erwin Brandstetter
@ ErwinBrandstetter Merci, a mis à jour ma question.
Yang
Je suppose que vous avez vérifié via explain analyzel'utilisation d'un index pour la recherche?
rogerdpack

Réponses:

45

Hypothèses

Comme il manque des informations dans le Q, je suppose:

  • Vos données proviennent d'un fichier sur le serveur de base de données.
  • Les données sont formatées de la même manière que la COPYsortie, avec un numéro unique id par ligne correspondant à la table cible.
    Sinon, formatez-le correctement en premier ou utilisez les COPYoptions pour gérer le format.
  • Vous mettez à jour chaque ligne de la table cible ou la plupart d'entre elles.
  • Vous pouvez vous permettre de supprimer et de recréer la table cible.
    Cela signifie pas d' accès simultané. Sinon, considérez cette réponse:
  • Il n'y a aucun objet dépendant du tout, à l'exception des index.

Solution

Je suggère que vous adoptiez une approche similaire à celle décrite au lien de votre troisième puce . Avec des optimisations majeures.

Pour créer la table temporaire, il existe un moyen plus simple et plus rapide:

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

Une seule grande UPDATEd'une table temporaire dans la base de données sera plus rapide que les mises à jour individuelles de l'extérieur de la base de données de plusieurs ordres de grandeur.

Dans le modèle MVCC de PostgreSQL , un UPDATEmoyen de créer une nouvelle version de ligne et de marquer l'ancienne comme étant supprimée. C'est à peu près aussi cher qu'un INSERTet un DELETEcombiné. De plus, il vous laisse beaucoup de tuples morts. De toute façon, comme vous mettez à jour toute la table, il serait plus rapide de simplement créer une nouvelle table et de supprimer l’ancienne.

Si vous avez assez de RAM disponible, définissez temp_buffers(uniquement pour cette session!) Suffisamment haut pour contenir la table temporaire en RAM - avant toute chose.

Pour obtenir une estimation de la quantité de RAM nécessaire, exécutez un test avec un petit échantillon et utilisez les fonctions de taille d'objet db :

SELECT pg_size_pretty(pg_relation_size('tmp_tbl'));  -- complete size of table
SELECT pg_column_size(t) FROM tmp_tbl t LIMIT 10;  -- size of sample rows

Script complet

SET temp_buffers = '1GB';        -- example value

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

COPY tmp_tbl FROM '/absolute/path/to/file';

CREATE TABLE tbl_new AS
SELECT t.col1, t.col2, u.field1, u.field2
FROM   tbl     t
JOIN   tmp_tbl u USING (id);

-- Create indexes like in original table
ALTER TABLE tbl_new ADD PRIMARY KEY ...;
CREATE INDEX ... ON tbl_new (...);
CREATE INDEX ... ON tbl_new (...);

-- exclusive lock on tbl for a very brief time window!
DROP TABLE tbl;
ALTER TABLE tbl_new RENAME TO tbl;

DROP TABLE tmp_tbl; -- will also be dropped at end of session automatically

Charge simultanée

Les opérations simultanées sur la table (que j’avais exclues dans les hypothèses au début) attendent, une fois que la table est verrouillée près de la fin et échouent dès que la transaction est validée, car le nom de la table est résolu immédiatement en OID, mais la nouvelle table a un OID différent. La table reste cohérente, mais les opérations simultanées peuvent faire l'objet d'une exception et doivent être répétées. Détails dans cette réponse:

UPDATE route

Si vous devez (suivre) la UPDATEroute, supprimez tout index non nécessaire lors de la mise à jour, puis recréez-le. Il est beaucoup moins coûteux de créer un index en un seul morceau que de le mettre à jour pour chaque ligne. Cela peut également permettre des mises à jour HOT .

J'ai décrit une procédure similaire en utilisant UPDATEdans cette réponse étroitement liée sur SO .

 

Erwin Brandstetter
la source
1
En fait, je ne fais que mettre à jour 20% des lignes de la table cible - pas toutes, mais une partie suffisamment importante pour qu'une fusion soit probablement meilleure qu'une mise à jour aléatoire.
Yang
1
@AryehLeibTaurog: Cela ne devrait pas se produire depuis DROP TABLEen sort une Access Exclusive Lock. Quoi qu'il en soit, j'ai déjà énuméré les conditions préalables en haut de ma réponse: You can afford to drop and recreate the target table.il pourrait être utile de verrouiller la table au début de la transaction. Je vous suggère de commencer une nouvelle question avec tous les détails pertinents de votre situation afin que nous puissions aller au fond des choses.
Erwin Brandstetter
1
@ ErwinBrandstetter Intéressant. Cela semble dépendre de la version du serveur. J'ai reproduit l'erreur sur 8.4 et 9.1 à l' aide de l'adaptateur psycopg2 et du client psql . Sur 9.3, il n'y a pas d'erreur. Voir mes commentaires dans le premier script. Je ne sais pas s'il y a une question à poster ici, mais il vaut peut-être la peine de solliciter des informations sur l'une des listes postgresql.
Aryeh Leib Taurog
1
J'ai écrit une classe d'assistance simple en python pour automatiser le processus.
Aryeh Leib Taurog
3
Réponse très utile. En variante, il est possible de créer la table temporaire avec uniquement les colonnes à mettre à jour et les colonnes de référence, supprimer les colonnes à mettre à jour de la table d'origine, puis fusionner les tables à l'aide de CREATE TABLE tbl_new AS SELECT t.*, u.field1, u.field2 from tbl t NATURAL LEFT JOIN tmp_tbl u;, en LEFT JOINpermettant de conserver les lignes pour lesquelles il n'y a pas de mise à jour. Bien sûr, le NATURALpeut être changé en tout valide USING()ou ON.
Skippy le Grand Gourou
2

Si les données peuvent être rendues disponibles dans un fichier structuré, vous pouvez les lire avec un wrapper de données étrangères et effectuer une fusion sur la table cible.

David Aldridge
la source
3
Qu'entendez-vous par "fusionner sur la table cible"? Pourquoi utiliser FDW mieux que copier dans un tableau temporaire (comme suggéré dans la troisième puce de la question initiale)?
Yang
"Fusionner" comme dans la déclaration MERGE sql. L'utilisation de FDW vous permet de le faire sans l'étape supplémentaire consistant à copier les données dans une table temporaire. Je suppose que vous ne remplacez pas l'intégralité du jeu de données et qu'il y aurait une certaine quantité de données dans le fichier qui ne représenterait pas un changement par rapport au jeu de données actuel - si une quantité importante a changé, alors une complète le remplacement de la table pourrait en valoir la peine.
David Aldridge
1
@DavidAldridge: Bien que défini dans la norme SQL: 2003, il MERGEn'est pas encore implémenté dans PostgreSQL . Les implémentations dans d'autres SGBDR varient un peu. Considérons les informations de balise pour MERGEet UPSERT.
Erwin Brandstetter
@ ErwinBrandstetter [glurk] Oh oui tout à fait. Eh bien Merge est la cerise sur le gâteau, je suppose. Accéder aux données sans l'étape import-to-temporaire-table-step est vraiment le noeud de la technique FDW.
David Aldridge