Postgres : bloat, pg_repack et contraintes différées

Postgres : bloat, pg_repack et contraintes différées

L'effet du gonflement sur les tables et les index est largement connu et n'est pas présent uniquement dans Postgres. Il existe des moyens de résoudre ce problème dès le départ, comme VACUUM FULL ou CLUSTER, mais ils verrouillent les tables pendant le fonctionnement et ne peuvent donc pas toujours être utilisés.

L'article contiendra un peu de théorie sur la manière dont le ballonnement se produit, comment le combattre, sur les contraintes différées et les problèmes qu'elles entraînent lors de l'utilisation de l'extension pg_repack.

Cet article est rédigé sur la base de mon discours à PgConf.Russie 2020.

Pourquoi les ballonnements se produisent-ils ?

Postgres est basé sur un modèle multi-version (MVCC). Son essence est que chaque ligne du tableau peut avoir plusieurs versions, tandis que les transactions ne voient pas plus d'une de ces versions, mais pas nécessairement la même. Cela permet à plusieurs transactions de fonctionner simultanément et d’avoir pratiquement aucun impact les unes sur les autres.

Évidemment, toutes ces versions doivent être stockées. Postgres fonctionne avec la mémoire page par page et une page représente la quantité minimale de données pouvant être lues à partir du disque ou écrites. Regardons un petit exemple pour comprendre comment cela se produit.

Disons que nous avons une table à laquelle nous avons ajouté plusieurs enregistrements. De nouvelles données sont apparues sur la première page du fichier où le tableau est stocké. Il s'agit de versions actives de lignes qui sont disponibles pour d'autres transactions après une validation (pour plus de simplicité, nous supposerons que le niveau d'isolement est Lecture Commise).

Postgres : bloat, pg_repack et contraintes différées

Nous avons ensuite mis à jour l'une des entrées, marquant ainsi l'ancienne version comme n'étant plus pertinente.

Postgres : bloat, pg_repack et contraintes différées

Étape par étape, en mettant à jour et en supprimant les versions de lignes, nous nous sommes retrouvés avec une page dans laquelle environ la moitié des données sont des « déchets ». Ces données ne sont visibles pour aucune transaction.

Postgres : bloat, pg_repack et contraintes différées

Postgres a un mécanisme VIDE, qui nettoie les versions obsolètes et laisse la place aux nouvelles données. Mais s'il n'est pas configuré de manière suffisamment agressive ou s'il est occupé à travailler dans d'autres tables, des « données inutiles » subsistent et nous devons utiliser des pages supplémentaires pour de nouvelles données.

Ainsi, dans notre exemple, à un moment donné, le tableau sera composé de quatre pages, mais seulement la moitié contiendra des données en direct. En conséquence, en accédant au tableau, nous lirons beaucoup plus de données que nécessaire.

Postgres : bloat, pg_repack et contraintes différées

Même si VACUUM supprime désormais toutes les versions de lignes non pertinentes, la situation ne s'améliorera pas de façon spectaculaire. Nous disposerons d'espace libre dans des pages, voire des pages entières, pour de nouvelles lignes, mais nous lirons toujours plus de données que nécessaire.
D'ailleurs, si une page complètement vierge (la deuxième dans notre exemple) se trouvait à la fin du fichier, alors VACUUM serait capable de la découper. Mais maintenant, elle est au milieu, donc on ne peut plus rien faire avec elle.

Postgres : bloat, pg_repack et contraintes différées

Lorsque le nombre de ces pages vides ou très clairsemées devient important, ce que l'on appelle le gonflement, cela commence à affecter les performances.

Tout ce qui est décrit ci-dessus concerne la mécanique de l'apparition de ballonnements dans les tableaux. Dans les index, cela se produit à peu près de la même manière.

Est-ce que j'ai des ballonnements ?

Il existe plusieurs façons de déterminer si vous avez des ballonnements. L'idée de la première est d'utiliser les statistiques internes de Postgres, qui contiennent des informations approximatives sur le nombre de lignes dans les tableaux, le nombre de lignes « en direct », etc. Vous pouvez trouver de nombreuses variantes de scripts prêts à l'emploi sur Internet. Nous avons pris comme base scénario des experts PostgreSQL, qui peuvent évaluer les tables de ballonnement ainsi que les index btree toast et bloat. D'après notre expérience, son erreur est de 10 à 20 %.

Une autre façon est d'utiliser l'extension pgstattuple, qui vous permet de regarder à l'intérieur des pages et d'obtenir à la fois une valeur de ballonnement estimée et exacte. Mais dans le second cas, vous devrez scanner l'intégralité du tableau.

Nous considérons qu'une petite valeur de ballonnement, jusqu'à 20 %, est acceptable. Il peut être considéré comme un analogue du fillfactor pour des tables и indices. À 50 % et plus, des problèmes de performances peuvent commencer.

Moyens de lutter contre les ballonnements

Postgres propose plusieurs méthodes prêtes à l'emploi pour gérer les ballonnements, mais elles ne conviennent pas toujours à tout le monde.

Configurez AUTOVACUUM pour éviter les ballonnements. Ou plus précisément, de le maintenir à un niveau acceptable pour vous. Cela ressemble à un conseil de « capitaine », mais en réalité ce n’est pas toujours facile à mettre en œuvre. Par exemple, vous avez un développement actif avec des modifications régulières du schéma de données, ou une sorte de migration de données est en cours. Par conséquent, votre profil de charge peut changer fréquemment et varie généralement d'une table à l'autre. Cela signifie que vous devez constamment travailler un peu en avance et adapter AUTOVACUUM au profil changeant de chaque table. Mais ce n’est évidemment pas facile à faire.

Une autre raison courante pour laquelle AUTOVACUUM ne peut pas suivre les tables est qu'il existe des transactions de longue durée qui l'empêchent de nettoyer les données disponibles pour ces transactions. La recommandation ici est également évidente : se débarrasser des transactions « en suspens » et minimiser le temps des transactions actives. Mais si la charge sur votre application est un hybride d'OLAP et d'OLTP, vous pouvez alors avoir simultanément de nombreuses mises à jour fréquentes et requêtes courtes, ainsi que des opérations à long terme - par exemple, créer un rapport. Dans une telle situation, il convient de penser à répartir la charge sur différentes bases, ce qui permettra d'affiner chacune d'entre elles.

Un autre exemple - même si le profil est homogène, mais que la base de données est soumise à une charge très élevée, même l'AUTOVACUUM le plus agressif peut ne pas y faire face et des ballonnements se produiront. La mise à l'échelle (verticale ou horizontale) est la seule solution.

Que faire dans une situation où vous avez installé AUTOVACUUM, mais que le ballonnement continue de croître.

Équipe VIDE PLEIN reconstruit le contenu des tables et des index et n'y laisse que les données pertinentes. Pour éliminer les ballonnements, cela fonctionne parfaitement, mais lors de son exécution, un verrou exclusif sur la table est capturé (AccessExclusiveLock), ce qui ne permettra pas d'exécuter des requêtes sur cette table, même des sélections. Si vous pouvez vous permettre d'arrêter votre service ou une partie de celui-ci pendant un certain temps (de quelques dizaines de minutes à plusieurs heures selon la taille de la base de données et de votre matériel), alors cette option est la meilleure. Malheureusement, nous n'avons pas le temps d'exécuter VACUUM FULL pendant la maintenance planifiée, cette méthode ne nous convient donc pas.

Équipe CLUSTER Reconstruit le contenu des tables de la même manière que VACUUM FULL, mais permet de spécifier un index selon lequel les données seront physiquement ordonnées sur le disque (mais à l'avenir l'ordre n'est pas garanti pour les nouvelles lignes). Dans certaines situations, il s'agit d'une bonne optimisation pour un certain nombre de requêtes - avec lecture de plusieurs enregistrements par index. L'inconvénient de la commande est le même que celui de VACUUM FULL : elle verrouille la table pendant le fonctionnement.

Équipe REINDEX similaire aux deux précédents, mais reconstruit un index spécifique ou tous les index de la table. Les verrous sont légèrement plus faibles : ShareLock sur la table (empêche les modifications, mais autorise la sélection) et AccessExclusiveLock sur l'index en cours de reconstruction (bloque les requêtes utilisant cet index). Cependant, dans la 12ème version de Postgres, un paramètre est apparu CONCURRENTEMENT, qui vous permet de reconstruire l'index sans bloquer l'ajout, la modification ou la suppression simultanée d'enregistrements.

Dans les versions antérieures de Postgres, vous pouvez obtenir un résultat similaire à REINDEX CONCURRENTLY en utilisant CRÉER UN INDEX SIMULTANÉMENT. Il permet de créer un index sans verrouillage strict (ShareUpdateExclusiveLock, qui n'interfère pas avec les requêtes parallèles), puis de remplacer l'ancien index par un nouveau et de supprimer l'ancien index. Cela vous permet d’éliminer le gonflement de l’index sans interférer avec votre application. Il est important de considérer que lors de la reconstruction des index, une charge supplémentaire sera exercée sur le sous-système de disque.

Ainsi, s’il existe des moyens d’éliminer les ballonnements « à la volée » pour les index, il n’en existe pas pour les tables. C’est là que diverses extensions externes entrent en jeu : pg_repack (anciennement pg_reorg), pgcompact, pgcompacttable et d'autres. Dans cet article, je ne les comparerai pas et ne parlerai que de pg_repack, que nous utilisons nous-mêmes après quelques modifications.

Comment fonctionne pg_repack

Postgres : bloat, pg_repack et contraintes différées
Disons que nous avons une table tout à fait ordinaire - avec des index, des restrictions et, malheureusement, avec des ballonnements. La première étape de pg_repack consiste à créer une table de journal pour stocker les données sur toutes les modifications pendant son exécution. Le déclencheur répliquera ces modifications pour chaque insertion, mise à jour et suppression. Ensuite, une table est créée, similaire à celle d'origine dans sa structure, mais sans index ni restrictions, afin de ne pas ralentir le processus d'insertion des données.

Ensuite, pg_repack transfère les données de l'ancienne table vers la nouvelle table, en filtrant automatiquement toutes les lignes non pertinentes, puis crée des index pour la nouvelle table. Lors de l'exécution de toutes ces opérations, les modifications s'accumulent dans la table journal.

L'étape suivante consiste à transférer les modifications vers la nouvelle table. La migration est effectuée sur plusieurs itérations, et lorsqu'il reste moins de 20 entrées dans la table de journal, pg_repack acquiert un verrou fort, migre les dernières données et remplace l'ancienne table par la nouvelle dans les tables système Postgres. C'est la seule et très courte période pendant laquelle vous ne pourrez pas travailler avec la table. Après cela, l'ancienne table et la table avec les journaux sont supprimées et de l'espace est libéré dans le système de fichiers. Le processus est terminé.

En théorie, tout semble bien, mais que se passe-t-il en pratique ? Nous avons testé pg_repack sans charge et sous charge, et vérifié son fonctionnement en cas d'arrêt prématuré (c'est-à-dire en utilisant Ctrl+C). Tous les tests étaient positifs.

Nous sommes allés au magasin d’alimentation et tout ne s’est pas passé comme prévu.

Première crêpe en vente

Sur le premier cluster, nous avons reçu une erreur concernant une violation d'une contrainte d'unicité :

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Cette limitation avait un nom généré automatiquement index_16508 - elle a été créée par pg_repack. A partir des attributs entrant dans sa composition, nous avons déterminé « notre » contrainte qui lui correspond. Le problème s'est avéré qu'il ne s'agit pas d'une limitation tout à fait ordinaire, mais d'une limitation différée (contrainte différée), c'est à dire. sa vérification est effectuée plus tard que la commande sql, ce qui entraîne des conséquences inattendues.

Contraintes différées : pourquoi elles sont nécessaires et comment elles fonctionnent

Une petite théorie sur les restrictions différées.
Prenons un exemple simple : nous avons un tableau de référence des voitures avec deux attributs : le nom et l'ordre de la voiture dans l'annuaire.
Postgres : bloat, pg_repack et contraintes différées

create table cars
(
  name text constraint pk_cars primary key,
  ord integer not null constraint uk_cars unique
);



Disons que nous devions échanger la première et la deuxième voiture. La solution simple consiste à mettre à jour la première valeur avec la seconde et la seconde avec la première :

begin;
  update cars set ord = 2 where name = 'audi';
  update cars set ord = 1 where name = 'bmw';
commit;

Mais lorsque nous exécutons ce code, nous nous attendons à une violation de contrainte car l'ordre des valeurs dans le tableau est unique :

[23305] ERROR: duplicate key value violates unique constraint “uk_cars”
Detail: Key (ord)=(2) already exists.

Comment puis-je procéder différemment ? Première option : ajoutez une valeur de remplacement supplémentaire à une commande dont il est garanti qu'elle n'existe pas dans le tableau, par exemple « -1 ». En programmation, cela s'appelle « échanger les valeurs de deux variables par une troisième ». Le seul inconvénient de cette méthode est la mise à jour supplémentaire.

Deuxième option : reconcevoir le tableau pour utiliser un type de données à virgule flottante pour la valeur de commande au lieu d'entiers. Ensuite, lors de la mise à jour de la valeur de 1, par exemple, à 2.5, la première entrée se « placera » automatiquement entre la deuxième et la troisième. Cette solution fonctionne, mais présente deux limites. Premièrement, cela ne fonctionnera pas pour vous si la valeur est utilisée quelque part dans l'interface. Deuxièmement, en fonction de la précision du type de données, vous disposerez d'un nombre limité d'insertions possibles avant de recalculer les valeurs de tous les enregistrements.

Troisième option : rendre la contrainte différée afin qu'elle ne soit vérifiée qu'au moment de la validation :

create table cars
(
  name text constraint pk_cars primary key,
  ord integer not null constraint uk_cars unique deferrable initially deferred
);

Puisque la logique de notre requête initiale garantit que toutes les valeurs sont uniques au moment de la validation, elle réussira.

L’exemple évoqué ci-dessus est certes très synthétique, mais il révèle l’idée. Dans notre application, nous utilisons des contraintes différées pour implémenter une logique chargée de résoudre les conflits lorsque les utilisateurs travaillent simultanément avec des objets widget partagés sur le tableau. L'utilisation de telles restrictions nous permet de simplifier un peu le code de l'application.

En général, selon le type de contrainte, Postgres dispose de trois niveaux de granularité pour les vérifier : les niveaux de ligne, de transaction et d'expression.
Postgres : bloat, pg_repack et contraintes différées
Source: begriffs

CHECK et NOT NULL sont toujours vérifiés au niveau de la ligne ; pour d'autres restrictions, comme le montre le tableau, il existe différentes options. Vous pouvez en lire davantage ici.

Pour résumer brièvement, les contraintes différées dans un certain nombre de situations fournissent un code plus lisible et moins de commandes. Cependant, vous devez payer pour cela en compliquant le processus de débogage, car le moment où l'erreur se produit et le moment où vous en découvrez sont séparés dans le temps. Un autre problème possible est que l'ordonnanceur n'est pas toujours en mesure de construire un plan optimal si la requête implique une contrainte différée.

Amélioration de pg_repack

Nous avons expliqué ce que sont les contraintes différées, mais quel est leur lien avec notre problème ? Rappelons-nous l'erreur que nous avons reçue plus tôt :

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Cela se produit lorsque les données sont copiées d'une table de journal vers une nouvelle table. Cela semble étrange parce que... les données de la table journal sont validées avec les données de la table source. S'ils satisfont aux contraintes de la table d'origine, comment peuvent-ils violer les mêmes contraintes dans la nouvelle ?

Il s'avère que la racine du problème réside dans l'étape précédente de pg_repack, qui crée uniquement des index, mais pas des contraintes : l'ancienne table avait une contrainte unique, et la nouvelle créait un index unique à la place.

Postgres : bloat, pg_repack et contraintes différées

Il est important de noter ici que si la contrainte est normale et non différée, alors l'index unique créé à la place est équivalent à cette contrainte, car Les contraintes uniques dans Postgres sont implémentées en créant un index unique. Mais dans le cas d'une contrainte différée, le comportement n'est pas le même, car l'index ne peut pas être différé et est toujours vérifié au moment de l'exécution de la commande sql.

Ainsi, l'essence du problème réside dans le « retard » de la vérification : dans la table d'origine, elle se produit au moment de la validation, et dans la nouvelle table au moment de l'exécution de la commande sql. Cela signifie que nous devons veiller à ce que les contrôles soient effectués de la même manière dans les deux cas : soit toujours en différé, soit toujours immédiatement.

Alors, quelles idées avions-nous ?

Créer un index similaire à différé

La première idée est d'effectuer les deux vérifications en mode immédiat. Cela peut générer plusieurs restrictions de faux positifs, mais s'il y en a peu, cela ne devrait pas affecter le travail des utilisateurs, car de tels conflits sont une situation normale pour eux. Ils se produisent, par exemple, lorsque deux utilisateurs commencent à éditer le même widget en même temps et que le client du deuxième utilisateur n'a pas le temps de recevoir des informations indiquant que le widget est déjà bloqué pour édition par le premier utilisateur. Dans une telle situation, le serveur refuse le deuxième utilisateur et son client annule les modifications et bloque le widget. Un peu plus tard, lorsque le premier utilisateur aura terminé l'édition, le second recevra l'information que le widget n'est plus bloqué et pourra répéter son action.

Postgres : bloat, pg_repack et contraintes différées

Pour garantir que les contrôles sont toujours en mode non différé, nous avons créé un nouvel index similaire à la contrainte différée d'origine :

CREATE UNIQUE INDEX CONCURRENTLY uk_tablename__immediate ON tablename (id, index);
-- run pg_repack
DROP INDEX CONCURRENTLY uk_tablename__immediate;

Dans l'environnement de test, nous n'avons reçu que quelques erreurs attendues. Succès! Nous avons réexécuté pg_repack en production et avons obtenu 5 erreurs sur le premier cluster en une heure de travail. C'est un résultat acceptable. Cependant, déjà sur le deuxième cluster, le nombre d'erreurs a considérablement augmenté et nous avons dû arrêter pg_repack.

Pourquoi est-ce arrivé ? La probabilité qu'une erreur se produise dépend du nombre d'utilisateurs travaillant simultanément avec les mêmes widgets. Apparemment, à ce moment-là, il y avait beaucoup moins de changements compétitifs avec les données stockées sur le premier cluster que sur les autres, c'est-à-dire nous avons simplement eu de la « chance ».

L'idée n'a pas fonctionné. À ce stade, nous avons vu deux autres solutions : réécrire notre code d'application pour nous passer des contraintes différées, ou « apprendre » à pg_repack à travailler avec elles. Nous avons choisi le deuxième.

Remplacer les index de la nouvelle table par des contraintes différées de la table d'origine

Le but de la révision était évident - si la table d'origine a une contrainte différée, alors pour la nouvelle, vous devez créer une telle contrainte, et non un index.

Pour tester nos modifications, nous avons écrit un test simple :

  • table avec une contrainte différée et un enregistrement ;
  • insérer des données dans une boucle qui entre en conflit avec un enregistrement existant ;
  • effectuez une mise à jour – les données ne sont plus en conflit ;
  • valider les modifications.

create table test_table
(
  id serial,
  val int,
  constraint uk_test_table__val unique (val) deferrable initially deferred 
);

INSERT INTO test_table (val) VALUES (0);
FOR i IN 1..10000 LOOP
  BEGIN
    INSERT INTO test_table VALUES (0) RETURNING id INTO v_id;
    UPDATE test_table set val = i where id = v_id;
    COMMIT;
  END;
END LOOP;

La version originale de pg_repack plantait toujours à la première insertion, la version modifiée fonctionnait sans erreur. Super.

Nous passons en production et obtenons à nouveau une erreur lors de la même phase de copie des données de la table journal vers une nouvelle :

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Situation classique : tout fonctionne dans les environnements de test, mais pas en production ?!

APPLY_COUNT et la jonction de deux lots

Nous avons commencé à analyser le code littéralement ligne par ligne et avons découvert un point important : les données sont transférées de la table log vers une nouvelle par lots, la constante APPLY_COUNT indiquait la taille du lot :

for (;;)
{
num = apply_log(connection, table, APPLY_COUNT);

if (num > MIN_TUPLES_BEFORE_SWITCH)
     continue;  /* there might be still some tuples, repeat. */
...
}

Le problème est que les données de la transaction d'origine, dans laquelle plusieurs opérations pourraient potentiellement violer la contrainte, une fois transférées, peuvent se retrouver à la jonction de deux lots - la moitié des commandes seront validées dans le premier lot et l'autre moitié dans la seconde. Et ici, selon votre chance : si les équipes ne violent rien dans le premier lot, alors tout va bien, mais si elles le font, une erreur se produit.

APPLY_COUNT est égal à 1000 enregistrements, ce qui explique pourquoi nos tests ont été réussis : ils ne couvraient pas le cas de la « jonction par lots ». Nous avons utilisé deux commandes - insert et update, donc exactement 500 transactions de deux commandes ont toujours été placées dans un lot et nous n'avons rencontré aucun problème. Après avoir ajouté la deuxième mise à jour, notre modification a cessé de fonctionner :

FOR i IN 1..10000 LOOP
  BEGIN
    INSERT INTO test_table VALUES (1) RETURNING id INTO v_id;
    UPDATE test_table set val = i where id = v_id;
    UPDATE test_table set val = i where id = v_id; -- one more update
    COMMIT;
  END;
END LOOP;

Ainsi, la tâche suivante consiste à s'assurer que les données de la table d'origine, qui ont été modifiées au cours d'une transaction, se retrouvent également dans la nouvelle table au cours d'une seule transaction.

Refus de regroupement

Et encore une fois, nous avions deux solutions. Premièrement : abandonnons complètement le partitionnement en lots et transférons les données en une seule transaction. L'avantage de cette solution était sa simplicité - les modifications de code requises étaient minimes (d'ailleurs, dans les anciennes versions, pg_reorg fonctionnait exactement comme ça). Mais il y a un problème : nous créons une transaction à long terme, ce qui, comme nous l’avons dit précédemment, constitue une menace pour l’émergence d’une nouvelle ballonnement.

La deuxième solution est plus complexe, mais probablement plus correcte : créer une colonne dans la table log avec l'identifiant de la transaction qui a ajouté des données à la table. Ensuite, lorsque nous copions des données, nous pouvons les regrouper selon cet attribut et garantir que les modifications associées sont transférées ensemble. Le lot sera formé de plusieurs transactions (ou d'une seule grande) et sa taille variera en fonction de la quantité de données modifiées dans ces transactions. Il est important de noter que puisque les données de différentes transactions entrent dans la table de journal dans un ordre aléatoire, il ne sera plus possible de les lire séquentiellement, comme c'était le cas auparavant. seqscan pour chaque requête avec filtrage par tx_id est trop cher, un index est nécessaire, mais cela ralentira également la méthode en raison de la surcharge de sa mise à jour. En général, comme toujours, il faut sacrifier quelque chose.

Nous avons donc décidé de commencer par la première option, car elle est plus simple. Premièrement, il fallait comprendre si une transaction longue poserait un réel problème. Étant donné que le principal transfert de données de l'ancienne table vers la nouvelle s'effectue également en une seule transaction longue, la question s'est transformée en « de combien allons-nous augmenter cette transaction ? La durée de la première transaction dépend principalement de la taille de la table. La durée d'un nouveau dépend du nombre de modifications accumulées dans le tableau pendant le transfert de données, c'est-à-dire sur l'intensité de la charge. L'exécution de pg_repack s'est produite à une période de charge de service minimale et le volume des modifications était disproportionné par rapport à la taille d'origine de la table. Nous avons décidé que nous pouvions négliger le temps d'une nouvelle transaction (à titre de comparaison, il est en moyenne de 1 heure et 2-3 minutes).

Les expériences étaient positives. Lancement en production également. Pour plus de clarté, voici une image avec la taille de l'une des bases de données après exécution :

Postgres : bloat, pg_repack et contraintes différées

Puisque nous avons été entièrement satisfaits de cette solution, nous n'avons pas essayé d'implémenter la seconde, mais nous réfléchissons à la possibilité d'en discuter avec les développeurs de l'extension. Malheureusement, notre révision actuelle n'est pas encore prête à être publiée, car nous n'avons résolu le problème qu'avec des restrictions différées uniques, et pour un correctif à part entière, il est nécessaire de prendre en charge d'autres types. Nous espérons pouvoir le faire à l'avenir.

Peut-être avez-vous une question, pourquoi nous sommes-nous même impliqués dans cette histoire avec la modification de pg_repack, et n'avons-nous pas, par exemple, utilisé ses analogues ? À un moment donné, nous y avons également pensé, mais l'expérience positive de son utilisation antérieure, sur des tables sans contraintes différées, nous a motivés à essayer de comprendre l'essence du problème et de le résoudre. De plus, l'utilisation d'autres solutions nécessite également du temps pour effectuer des tests, nous avons donc décidé d'essayer d'abord de résoudre le problème, et si nous réalisions que nous ne pouvions pas le faire dans un délai raisonnable, nous commencerions à chercher des analogues. .

résultats

Ce que nous pouvons recommander sur la base de notre propre expérience :

  1. Surveillez vos ballonnements. Sur la base des données de surveillance, vous pouvez comprendre dans quelle mesure l'autovacuum est configuré.
  2. Ajustez AUTOVACUUM pour maintenir les ballonnements à un niveau acceptable.
  3. Si la surcharge continue de croître et que vous ne parvenez pas à la surmonter à l'aide d'outils prêts à l'emploi, n'ayez pas peur d'utiliser des extensions externes. L'essentiel est de bien tout tester.
  4. N'ayez pas peur de modifier des solutions externes en fonction de vos besoins. Cela peut parfois s'avérer plus efficace et même plus simple que de modifier votre propre code.

Source: habr.com

Ajouter un commentaire