Autovacuum agressif sur PostgreSQL

42

J'essaie de faire en sorte que PostgreSQL ™ aspire de manière agressive ma base de données. J'ai actuellement configuré le vide automatique comme suit:

  • autovacuum_vacuum_cost_delay = 0 # Désactive le vide basé sur les coûts
  • autovacuum_vacuum_cost_limit = 10000 #Max
  • autovacuum_vacuum_threshold = 50 # Valeur par défaut
  • autovacuum_vacuum_scale_factor = 0.2 # Valeur par défaut

Je remarque que le vide automatique ne se déclenche que lorsque la base de données n'est pas chargée, alors je me retrouve dans des situations où il y a beaucoup plus de nuplets morts que de nuplets réels. Voir la capture d'écran ci-jointe pour un exemple. Une des tables a 23 tuples vivants mais 16845 tuples morts en attente d'aspiration. C'est dingue!

Beaucoup de tuples morts

Le vide automatique démarre lorsque le test est terminé et que le serveur de base de données est inactif, ce qui n'est pas ce que je veux car je voudrais que le vide automatique démarre lorsque le nombre de tuples morts dépasse 20% de tuples vivants + 50, comme l'a été la base de données configuré. L'aspiration automatique lorsque le serveur est inactif me sert à rien, car le serveur de production est censé atteindre des milliers de mises à jour / s pendant une période prolongée, raison pour laquelle j'ai besoin de l'aspiration automatique même si le serveur est chargé.

Y a-t-il quelque chose qui me manque? Comment forcer l'aspiration automatique à fonctionner lorsque le serveur est soumis à une charge importante?

Mise à jour

Cela pourrait-il être un problème de verrouillage? Les tables en question sont des tables récapitulatives qui sont remplies via un déclencheur après insertion. Ces tables sont verrouillées en mode SHARE ROW EXCLUSIVE pour empêcher les écritures simultanées sur la même ligne.

CadentOrange
la source

Réponses:

40

Eelke a presque certainement raison de penser que votre verrouillage bloque l'autovacuum. Autovacuum est conçu pour laisser la place aux activités des utilisateurs, délibérément. Si ces tables sont verrouillées, autovacuum ne peut pas les aspirer.

Pour la postérité, cependant, je voulais donner un exemple de paramètres pour l'autovacuum hyper-agressif, car les paramètres que vous avez définis ne le font pas tout à fait. Notez que rendre l'autovacuum plus agressif ne réglera probablement pas votre problème. Notez également que les paramètres autovacuum par défaut reposent sur l'exécution de plus de 200 tests en utilisant DBT2 afin d'obtenir une combinaison optimale de paramètres. Par conséquent, les valeurs par défaut doivent être considérées comme bonnes, à moins que vous n'ayez une raison valable de penser le contraire ou que votre base de données soit nettement à l'extérieur. le grand public pour les bases de données OLTP (par exemple, une petite base de données qui reçoit 10 000 mises à jour par seconde ou un entrepôt de données de 3 To).

Commencez par activer la journalisation afin de vérifier si autovacuum agit comme vous le pensez:

log_autovacuum_min_duration = 0

Alors faisons plus de travailleurs autovac et demandons-leur de vérifier les tables plus souvent:

autovacuum_max_workers = 6
autovacuum_naptime = 15s

Abaissons les seuils pour le vide automatique et l'analyse automatique pour déclencher plus tôt:

autovacuum_vacuum_threshold = 25
autovacuum_vacuum_scale_factor = 0.1

autovacuum_analyze_threshold = 10
autovacuum_analyze_scale_factor = 0.05 

Rendons ensuite autovacuum moins interruptible afin de terminer plus rapidement, mais au prix d'un impact plus important sur l'activité des utilisateurs simultanés:

autovacuum_vacuum_cost_delay = 10ms
autovacuum_vacuum_cost_limit = 1000

Votre programme complet pour l'autovacuum génériquement agressif pourrait convenir à une petite base de données générant un taux de mises à jour très élevé, mais pouvant également avoir un impact trop important sur l'activité des utilisateurs simultanés.

Notez également que les paramètres autovacuum peuvent être ajustés par table , ce qui est presque toujours une meilleure réponse pour avoir à ajuster le comportement de autovacuum.

Encore une fois, il est peu probable que vous régliez votre problème réel.

Josh Berkus
la source
36

Juste pour voir quelles tables sont qualifiées pour autovacuum, la requête suivante peut être utilisée (basée sur http://www.postgresql.org/docs/current/static/routine-vacuuming.html ). Notez cependant que la requête ne recherche pas de paramètres spécifiques à la table:

 SELECT psut.relname,
     to_char(psut.last_vacuum, 'YYYY-MM-DD HH24:MI') as last_vacuum,
     to_char(psut.last_autovacuum, 'YYYY-MM-DD HH24:MI') as last_autovacuum,
     to_char(pg_class.reltuples, '9G999G999G999') AS n_tup,
     to_char(psut.n_dead_tup, '9G999G999G999') AS dead_tup,
     to_char(CAST(current_setting('autovacuum_vacuum_threshold') AS bigint)
         + (CAST(current_setting('autovacuum_vacuum_scale_factor') AS numeric)
            * pg_class.reltuples), '9G999G999G999') AS av_threshold,
     CASE
         WHEN CAST(current_setting('autovacuum_vacuum_threshold') AS bigint)
             + (CAST(current_setting('autovacuum_vacuum_scale_factor') AS numeric)
                * pg_class.reltuples) < psut.n_dead_tup
         THEN '*'
         ELSE ''
     END AS expect_av
 FROM pg_stat_user_tables psut
     JOIN pg_class on psut.relid = pg_class.oid
 ORDER BY 1;
pygrac
la source
11

Oui c'est un problème de verrouillage. Selon cette page (non complet), VACUUM nécessite un accès SHARE UPDATE EXCLUSIVE qui est bloqué par le niveau de verrouillage que vous utilisez.

Êtes-vous certain d'avoir besoin de ce verrou? PostgreSQL est compatible ACID, donc les écritures simultanées ne posent généralement pas de problème, car PostgreSQL abandonnera l'une des transactions en cas de violation de la sérialisation.

Vous pouvez également verrouiller les lignes en utilisant SELECT FOR UPDATE pour verrouiller les lignes au lieu de la totalité de la table.

Une autre alternative sans verrouillage consisterait à utiliser le niveau d'isolation de transaction sérialisable. Toutefois, cela pourrait avoir une incidence sur les performances des autres transactions et vous devez être préparé à davantage d'échecs de sérialisation.

Eelke
la source
Cela est dû au verrouillage des tables récapitulatives, qui sont verrouillées à l'aide de SHARE ROW EXCLUSIVE MODE. Les écritures simultanées sans verrouillage peuvent réussir, mais elles vont très certainement se retrouver avec de mauvaises valeurs. Imaginez que je maintienne un nombre N de lignes de type X. Si j'insère simultanément deux lignes de type X, sans verrouillage, je vais me retrouver avec N + 1 dans mon tableau récapitulatif au lieu de N + 2. La solution que j'ai adoptée est d'avoir un travail cron qui vide manuellement les tables récapitulatives de ma base de données. Cela fonctionne bien et semble être l’approche recommandée, mais c’est trop pour moi.
CadentOrange
6

Augmenter le nombre de processus autovacuum et réduire le nombre de siestes aidera probablement. Voici la configuration pour PostgreSQL 9.1 que j'utilise sur un serveur qui stocke les informations de sauvegarde et génère par conséquent beaucoup d'activité d'insertion.

http://www.postgresql.org/docs/current/static/runtime-config-autovacuum.html

autovacuum_max_workers = 6              # max number of autovacuum subprocesses
autovacuum_naptime = 10         # time between autovacuum runs
autovacuum_vacuum_cost_delay = 20ms     # default vacuum cost delay for

Je vais aussi essayer de baisser le cost_delay pour rendre l’aspiration plus agressive.

Je peux aussi tester l'autovacuuming en utilisant pgbench.

http://wiki.postgresql.org/wiki/Pgbenchtesting

Exemple de conflit élevé:

Créer une base de données bench_replication

pgbench -i -p 5433 bench_replication

Exécuter pgbench

pgbench -U postgres -p 5432 -c 64 -j 4 -T 600 bench_replication

Vérifier l'état de l'autovacuuming

psql
>\connect bench_replicaiton
bench_replication=# select schemaname, relname, last_autovacuum from pg_stat_user_tables;
 schemaname |     relname      |        last_autovacuum        
------------+------------------+-------------------------------
 public     | pgbench_branches | 2012-07-18 18:15:34.494932+02
 public     | pgbench_history  | 
 public     | pgbench_tellers  | 2012-07-18 18:14:06.526437+02
 public     | pgbench_accounts | 
Craig Efrein
la source
6

Le script existant "qualifier pour autovacuum" est très utile, mais (comme indiqué correctement), il manquait des options spécifiques à la table. Voici une version modifiée de celle-ci qui prend en compte ces options:

WITH rel_set AS
(
    SELECT
        oid,
        CASE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_threshold=', 2), ',', 1)
            WHEN '' THEN NULL
        ELSE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_threshold=', 2), ',', 1)::BIGINT
        END AS rel_av_vac_threshold,
        CASE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_scale_factor=', 2), ',', 1)
            WHEN '' THEN NULL
        ELSE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_scale_factor=', 2), ',', 1)::NUMERIC
        END AS rel_av_vac_scale_factor
    FROM pg_class
) 
SELECT
    PSUT.relname,
    to_char(PSUT.last_vacuum, 'YYYY-MM-DD HH24:MI')     AS last_vacuum,
    to_char(PSUT.last_autovacuum, 'YYYY-MM-DD HH24:MI') AS last_autovacuum,
    to_char(C.reltuples, '9G999G999G999')               AS n_tup,
    to_char(PSUT.n_dead_tup, '9G999G999G999')           AS dead_tup,
    to_char(coalesce(RS.rel_av_vac_threshold, current_setting('autovacuum_vacuum_threshold')::BIGINT) + coalesce(RS.rel_av_vac_scale_factor, current_setting('autovacuum_vacuum_scale_factor')::NUMERIC) * C.reltuples, '9G999G999G999') AS av_threshold,
    CASE
        WHEN (coalesce(RS.rel_av_vac_threshold, current_setting('autovacuum_vacuum_threshold')::BIGINT) + coalesce(RS.rel_av_vac_scale_factor, current_setting('autovacuum_vacuum_scale_factor')::NUMERIC) * C.reltuples) < PSUT.n_dead_tup
        THEN '*'
    ELSE ''
    END AS expect_av
FROM
    pg_stat_user_tables PSUT
    JOIN pg_class C
        ON PSUT.relid = C.oid
    JOIN rel_set RS
        ON PSUT.relid = RS.oid
ORDER BY C.reltuples DESC;
Vadim Zingertal
la source